Chicago Postgres User Group Meetup, January 13, 2020





Parfait Gasana

Data Analyst, Winston & Strawn
@Parfait (StackOverflow) |   @ParfaitG (GitHub)


Open Source Success Stories


Challenging Commercial Market Share







Relational Database Solution


Use Case: Environment Database

Human Impact on the Global and Local Biosphere and Climate



suppressMessages(library(kableExtra))
suppressMessages(library(xts))
seaborn_palette <- c("#4C72B0", "#DD8452", "#55A868", "#C44E52", "#8172B3", "#937860", 
                     "#DA8BC3", "#8C8C8C", "#CCB974", "#64B5CD", "#4C72B0", "#DD8452")

Connection

ODBC

library(DBI)
library(odbc)
conn <- dbConnect(odbc::odbc(), driver="PostgreSQL Unicode", 
                  server="localhost", database="environment",
                  uid="postgres", pwd="env19", port=6432)
dbListTables(conn)
 [1] "ag_census"             "arable_land"           "consumption"           "fws_species_count"     "fws_species_year"     
 [6] "global_mean_sea_level" "global_temperature"    "groundwater"           "iucn_species_count"    "ocean_data"           
[11] "plants_assessments"    "ppm_month"             "ppm_week"              "sea_ice_extent"        "us_co2_emissions"     
[16] "us_gdp"                "us_population"         "us_renewable_energy"   "world_co2_emissions"   "world_gdp"            
[21] "world_population"     
dbDisconnect(conn)

JDBC

library(rJava)
library(RJDBC)
drv <- JDBC("org.postgresql.Driver",
           "/usr/lib/jvm/java-8-oracle/lib/postgresql-42.2.2.jar")
conn <- dbConnect(drv, "jdbc:postgresql://localhost:6432/environment", "postgres", "env19")
dbListTables(conn, schema="public")
 [1] "ag_census_pkey"               "arable_land_pkey"             "co2_emissions_pkey"          
 [4] "consumption_pkey"             "fws_species_count_pkey"       "fws_species_year_pkey"       
 [7] "global_mean_sea_level_pkey"   "global_temperature_pkey"      "groundwater_pkey"            
[10] "iucn_species_count_pkey"      "ocean_data_pkey"              "plants_assessments_pkey"     
[13] "ppm_month_pkey"               "ppm_week_pkey"                "sea_ice_extent_pkey"         
[16] "us_gdp_pkey"                  "us_renewable_energy_pkey"     "usa_population_pkey"         
[19] "world_co2_emissions_pkey"     "world_gdp_pkey"               "world_population_pkey"       
[22] "ag_census_id_seq"             "arable_land_id_seq"           "co2_emissions_id_seq"        
[25] "consumption_id_seq"           "fws_species_count_id_seq"     "fws_species_year_id_seq"     
[28] "global_mean_sea_level_id_seq" "global_temperature_id_seq"    "groundwater_id_seq"          
[31] "iucn_species_count_id_seq"    "ocean_data_id_seq"            "plants_assessments_id_seq"   
[34] "ppm_month_id_seq"             "ppm_week_id_seq"              "sea_ice_extent_id_seq"       
[37] "us_gdp_id_seq"                "us_renewable_energy_id_seq"   "usa_population_id_seq"       
[40] "world_co2_emissions_id_seq"   "world_gdp_id_seq"             "world_population_id_seq"     
[43] "ag_census"                    "arable_land"                  "consumption"                 
[46] "fws_species_count"            "fws_species_year"             "global_mean_sea_level"       
[49] "global_temperature"           "groundwater"                  "iucn_species_count"          
[52] "ocean_data"                   "plants_assessments"           "ppm_month"                   
[55] "ppm_week"                     "sea_ice_extent"               "us_co2_emissions"            
[58] "us_gdp"                       "us_population"                "us_renewable_energy"         
[61] "world_co2_emissions"          "world_gdp"                    "world_population"            
dbDisconnect(conn)
[1] TRUE

R-Postgres API

library(RPostgreSQL)
conn <- dbConnect(RPostgreSQL::PostgreSQL(), host="localhost", dbname="environment",
                  user="postgres", password="env19", port=6432)
dbListTables(conn)
 [1] "ag_census"             "plants_assessments"    "us_gdp"                "us_renewable_energy"   "world_gdp"            
 [6] "world_co2_emissions"   "global_mean_sea_level" "groundwater"           "ocean_data"            "us_co2_emissions"     
[11] "us_population"         "ppm_month"             "ppm_week"              "consumption"           "fws_species_count"    
[16] "fws_species_year"      "iucn_species_count"    "global_temperature"    "sea_ice_extent"        "world_population"     
[21] "arable_land"          

Import & Manage Data

CREATE TABLE global_temperature (
   id SERIAL NOT NULL PRIMARY KEY,
   year INT,
   period VARCHAR(50),
   global_mean NUMERIC(5,2)
);

\copy global_temperature (year, period, global_mean) FROM '/home/parfaitg/Databases/SQL_Server/ENVIRONMENT/global_temperature.csv' DELIMITER ',' CSV HEADER;

COMMENT ON TABLE global_temperature IS 'Source: NASA - Combined Land-Surface Air and Sea-Surface Water Temperature Anomalies (Land-Ocean Temperature Index, LOTI) (https://data.giss.nasa.gov/gistemp/)';
SELECT relname as table, obj_description(oid) as comment
FROM pg_class
WHERE relkind = 'r'
  AND obj_description(oid) IS NOT NULL
  ORDER BY relname
kable_styling(kable(comments_df),
              bootstrap_options = c("striped", "hover"))
table comment
ag_census U.S. Department of Agriculture, Census Data Query Tool (CDQT): https://www.nass.usda.gov/Quick_Stats/CDQT/chapter/1/table/1
arable_land Source: World Bank - Arable land (% of land area) (https://data.worldbank.org/indicator/AG.LND.ARBL.ZS?end=2016&start=1961&view=chart)
consumption Source: EIA - Monthly Energy Review, publication of recent and historical energy statistics (https://www.eia.gov/totalenergy/data/monthly/)
fws_species_count Source: US FWS - U.S. Federal Endangered and Threatened Species (https://ecos.fws.gov/ecp/species-reports)
fws_species_year Source: US FWS - U.S. Federal Endangered and Threatened Species (https://ecos.fws.gov/ecp/species-reports)
global_mean_sea_level Source: NASA - Global Mean Sea Level (mm): https://climate.nasa.gov/vital-signs/sea-level/
global_temperature Source: NASA - Combined Land-Surface Air and Sea-Surface Water Temperature Anomalies (Land-Ocean Temperature Index, LOTI) (https://data.giss.nasa.gov/gistemp/)
groundwater U.S. Geological Survey (USGS) Groundwater-Level Annual Statistics for the Nation: https://waterdata.usgs.gov/nwis/annual?referred_module=gw&search_criteria=site_tp_cd&submitted_form=introduction
iucn_species_count Source: IUCN - Red List of Threatened Species (https://www.iucnredlist.org/resources/summary-statistics#Summary%20Tables)
ocean_data Source: NOAA - Global Ocean Data Analysis Project (https://data.nodc.noaa.gov/cgi-bin/iso?id=gov.noaa.nodc:0162565#)
plants_assessments Source: Botanic Gardens Conservation International - Plant Threat Search: https://tools.bgci.org/threat_search.php
ppm_month Source: NOAA - Mauna Loa CO2 records Carbon PPM (https://www.esrl.noaa.gov/gmd/ccgg/trends/data.html)
ppm_week Source: NOAA - Mauna Loa CO2 records Carbon PPM (https://www.esrl.noaa.gov/gmd/ccgg/trends/data.html)
sea_ice_extent Source: NSIDC - Sea Ice Index Data (https://nsidc.org/data/seaice_index/archives)
us_co2_emissions Source: EIA - Monthly Energy Review, publication of recent and historical energy statistics (https://www.eia.gov/totalenergy/data/monthly/)
us_gdp Source: Federal Reserve Bank of St. Louis - US Real Gross Domestic Product by Industry: https://fred.stlouisfed.org/categories/33045
us_population Source: Federal Reserve Bank of St. Louis - U.S. Monthly Population (https://fred.stlouisfed.org/series/POPTHM)
us_renewable_energy Source: EIA - Renewable Energy Production and Consumption by Source: https://www.eia.gov/totalenergy/data/monthly/
world_co2_emissions Source: World Bank - World CO2 emissions (kt): https://data.worldbank.org/indicator/EN.ATM.CO2E.KT?end=2014&start=1960&view=chart
world_gdp Source: World Bank - World GDP (Current US$): https://data.worldbank.org/indicator/NY.GDP.MKTP.CD
world_population Source: World Bank - Arable land (% of land area) (https://data.worldbank.org/indicator/AG.LND.ARBL.ZS?end=2016&start=1961&view=chart)

Analyze Data

Department of Energy’s EIA: Monthly Energy Review &

NOAA Mauna Loa, Hawaii Observatory - Carbon PPM

SELECT p.date_year, p.date_month, CONCAT(p.date_year, '-', p.date_month, '-01')::date as date_day,
               p.average_ppm as "carbon ppm", c.energy_consumed as "btu consumed", e.energy_co2 as "co2 emissions"
  FROM ppm_month p
  JOIN consumption c 
    ON p.date_year = c.date_year AND p.date_month = c.date_month and c.msn = 'TXRCBUS'
  JOIN us_co2_emissions e
    ON p.date_year = e.date_year AND p.date_month = e.date_month and e.msn = 'TETCEUS'
  ORDER BY p.date_year, p.date_month
SELECT p.date_year,
       sum(p.average_ppm) as carbon_ppm_total, 
       avg(p.average_ppm) as carbon_ppm_mean, 
       sum(c.energy_consumed) as btu_consumed_total,
       avg(c.energy_consumed) as btu_consumed_mean,
       sum(e.energy_co2) as co2_emissions_total,
       avg(e.energy_co2) as co2_emissions_mean
  FROM ppm_month p
  JOIN consumption c 
    ON p.date_year = c.date_year AND p.date_month = c.date_month and c.msn = 'TXRCBUS'
  JOIN us_co2_emissions e
    ON p.date_year = e.date_year AND p.date_month = e.date_month and e.msn = 'TETCEUS'
GROUP BY p.date_year
ORDER BY p.date_year
kable_styling(kable(tail(agg_df, 10)),
              bootstrap_options = c("striped", "hover"))
date_year carbon_ppm_total carbon_ppm_mean btu_consumed_total btu_consumed_mean co2_emissions_total co2_emissions_mean
38 2010 4678.79 389.8992 6641.355 553.4462 5585.741 465.4784
39 2011 4699.83 391.6525 6473.666 539.4722 5446.133 453.8444
40 2012 4726.24 393.8533 5684.503 473.7086 5237.300 436.4417
41 2013 4758.25 396.5208 6689.368 557.4473 5363.018 446.9182
42 2014 4783.77 398.6475 7007.139 583.9283 5411.193 450.9327
43 2015 4810.01 400.8342 6465.092 538.7577 5264.776 438.7313
44 2016 4850.87 404.2392 6033.098 502.7582 5172.402 431.0335
45 2017 4878.64 406.5533 6111.580 509.2983 5130.589 427.5491
46 2018 4902.26 408.5217 6896.516 574.7097 5267.750 438.9792
47 2019 410.83 410.8300 1163.200 1163.2000 493.839 493.8390
metric_ts <- xts(x=metrics_df[c("carbon ppm", "btu consumed", "co2 emissions")], 
                 order.by=metrics_df$date_day)
plot(metric_ts, main = "Energy and Carbon PPM Metrics",
     legend.loc="bottomright", 
     col = seaborn_palette[1:3],
     yaxis.right=FALSE,
     axes=FALSE,
     lwd=1,
     cex.main=3,
     major.ticks="years",
     major.format="%Y",
     minor.format="%Y",
     grid.ticks.lty=1)

Seasonal Decomposition

carbonppm_ts <- ts(metrics_df$`carbon ppm`, start=c(1973, 1), frequency=12)
carbonppm_stl <- stl(carbonppm_ts, s.window="periodic")
plot(carbonppm_stl, col = seaborn_palette[1], 
     main="Seasonal Decomposition of Global Carbon PPM")

consumed_ts <- ts(metrics_df$`btu consumed`, start=c(1973, 1), frequency=12)
consumed_stl <- stl(consumed_ts, s.window="periodic")
plot(consumed_stl, col = seaborn_palette[2], 
     main="Seasonal Decomposition of U.S. Energy Consumption")

emissions_ts <- ts(metrics_df$`co2 emissions`, start=c(1973, 1), frequency=12)
emissions_stl <- stl(emissions_ts, s.window="periodic")
plot(emissions_stl, col = seaborn_palette[3],
     main = "Seasonal Decomposition of U.S. CO2 Emissions")

sql <- "WITH c1 AS
           (SELECT CONCAT((date_year/10)::int * 10, 's') as decade, 
                   REPLACE(description, 'Primary Energy Consumed by the ', '') AS \"Sector\",
                   SUM(energy_consumed) AS \"Primary Energy Consumed\"
            FROM consumption
            WHERE msn IN ('TXICBUS', 'TXRCBUS', 'TXACBUS', 'TXCCBUS', 'TXEIBUS')
            GROUP BY CONCAT((date_year/10)::int * 10, 's'), description
           ), c2 AS
           (SELECT CONCAT((date_year/10)::int * 10, 's') as decade, 
                   REPLACE(description, 'Total Energy Consumed by the ', '') AS \"Sector\",
                   SUM(energy_consumed) AS \"Total Energy Consumed\"
            FROM consumption
            WHERE msn IN ('TECCBUS', 'TEACBUS', 'TEICBUS', 'TERCBUS')
            GROUP BY CONCAT((date_year/10)::int * 10, 's'), description
           )
        SELECT c1.decade, c1.\"Sector\", c1.\"Primary Energy Consumed\", c2.\"Total Energy Consumed\"
        FROM c1
        LEFT JOIN c2
            ON c1.\"Sector\" = c2.\"Sector\" AND c1.decade = c2.decade
        ORDER BY c1.decade, c1.\"Sector\"
       "
consumed_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(consumed_df)),
              bootstrap_options = c("striped", "hover"))
decade Sector Primary Energy Consumed Total Energy Consumed
35 2000s Transportation Sector 546616.33 548112.4
36 2010s Commercial Sector 77171.71 326503.7
37 2010s Electric Power Sector 693591.66 NA
38 2010s Industrial Sector 388661.21 570178.3
39 2010s Residential Sector 117157.95 378492.5
40 2010s Transportation Sector 490149.36 491557.4

Consumption and CO2 Emissions

plot_mat <- with(subset(consumed_df, Sector != 'Electric Power Sector'),
                 tapply(`Total Energy Consumed`, list(decade, `Sector`), sum))
par(mar=c(5, 5, 2, 1))
barplot(plot_mat, main="U.S. Total Energy Consumption by Sector", cex.main=1.5,
        col=seaborn_palette[1:8], ylim=c(0, 8E5), xaxt="n", yaxt="n", beside=TRUE)
axis(side=1, at=c(5, 14, 23, 32), labels=colnames(plot_mat), padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:8], ncol=8)

plot_mat <- with(consumed_df, tapply(`Primary Energy Consumed`, list(decade, `Sector`), sum))
par(mar=c(5, 5, 2, 1))
barplot(plot_mat, main="U.S. Primary Energy Consumption by Sector", cex.main=1.5,
        col=seaborn_palette[1:8], ylim=c(0, 9E5), xaxt="n", yaxt="n", beside=TRUE)
axis(side=1, at=c(5, 14, 23, 32, 41), labels=colnames(plot_mat), padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:8], ncol=8)

sql <- "SELECT CONCAT((date_year/10)::int * 10, 's') as decade, 
               REPLACE(REPLACE(description, 'Total Energy ', ''), ' CO2 Emissions', '') AS \"Sector\",
               SUM(energy_co2) AS \"Total CO2 Emissions\"
        FROM us_co2_emissions
        WHERE msn IN ('TERCEUS', 'TECCEUS', 'TEACEUS', 'TXEIEUS')
        GROUP BY date_year, description
        ORDER BY date_year, description
       "
emissions_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(emissions_df)),
              bootstrap_options = c("striped", "hover"))
decade Sector Total CO2 Emissions
183 2010s Residential Sector 2034.691
184 2010s Transportation Sector 3830.401
185 2010s Commercial Sector 164.804
186 2010s Electric Power Sector 274.988
187 2010s Residential Sector 223.165
188 2010s Transportation Sector 295.360
plot_mat <- with(emissions_df, tapply(`Total CO2 Emissions`, list(decade, `Sector`), sum))
par(mar=c(5, 8, 2, 1))
barplot(plot_mat, main="U.S. CO2 Emissions by Sector", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 6E4), xaxt="n", yaxt="n", beside=TRUE)
axis(side=1, at=c(3, 9, 15, 21)+0.5, labels=colnames(plot_mat), padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

sql <- "WITH sub AS
          (SELECT CONCAT((date_year/10)::int * 10, 's') as decade, 
                  REPLACE($1, '%', '') as sector,
                  REPLACE(
                     REPLACE(
                       REPLACE(
                               REPLACE(description, 'Commercial Sector CO2 Emissions', ''), 
                               'Residential Sector CO2 Emissions', ''
                       ), 'Transportation Sector CO2 Emissions', ''),
                  ' ', '\n')  AS \"Type\",
                 energy_co2
           FROM us_co2_emissions
           WHERE description LIKE $2)
       SELECT decade, sector, \"Type\", 
              SUM(energy_co2) AS \"Total CO2 Emissions\"
       FROM sub
       GROUP BY decade, sector, \"Type\"
       ORDER BY decade, sector, \"Type\"
       "
params <- paste0(c("%Transportation", "%Residential", "%Commercial"), " Sector%")
emissions_type_df <- do.call(rbind, lapply(params, function(p) dbGetQuery(conn, sql, param=list(p, p))))
par(mfrow=c(3,2), mar=c(5, 5, 2, 1), mai = c(0.7, 0.2, 0.7, 0.2))
output <- by(emissions_type_df, emissions_type_df$sector, function(sub) {
  plot_mat <- with(sub, tapply(`Total CO2 Emissions`, list(decade, `Type`), sum))
  barplot(plot_mat, main=paste("U.S. CO2 Emissions by", sub$sector[[1]]), cex.main=1.5,
          col=seaborn_palette[1:5], ylim=c(0, ceiling(max(plot_mat, na.rm=TRUE)/1E4) * 1E4), 
          xaxt="n", yaxt="n", beside=TRUE)
  
  axis(side=1, at=c(3,9,15,21,27,33,39,45,51,57,63)[seq_along(colnames(plot_mat))] + 0.5,
       labels=colnames(plot_mat), padj=0.9, pos=c(0,0))
  axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
  legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)
})

Renewable Energy: Production and Consumption

sql <- "SELECT energy_type,
               date,
               SUM(production) AS production,
               SUM(consumption) AS consumption
        FROM us_renewable_energy
        GROUP BY energy_type,
                 date
        ORDER BY energy_type,
                 date
       "
renewable_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(renewable_df)),
              bootstrap_options = c("striped", "hover"))
energy_type date production consumption
4678 Wood Energy 2019-02-01 190.887 182.491
4679 Wood Energy 2019-03-01 198.621 191.507
4680 Wood Energy 2019-04-01 195.791 187.670
4681 Wood Energy 2019-05-01 201.743 193.775
4682 Wood Energy 2019-06-01 198.379 189.036
4683 Wood Energy 2019-07-01 205.023 196.873
par(mfrow=c(3,3), mar=c(5, 5, 2, 1))
output <- by(renewable_df, renewable_df$energy_type, function(sub) {
  
  metric_ts <- xts(x=sub[c("production", "consumption")], 
                   order.by=sub$date)
  
  print(plot(metric_ts, main = sub$energy_type[1],
             legend.loc="bottomright", 
             col = seaborn_palette[1:3],
             yaxis.right=FALSE,
             axes=FALSE,
             lwd=1,
             cex.main=3,
             major.ticks="years",
             major.format="%Y",
             minor.format="%Y",
             grid.ticks.lty=1))
  
})

U.S. Geological Survey - Groundwater Well Depth Data

sql <- "SELECT g.year, g.month,
               avg(g.mean_value) as mean_value
        FROM groundwater g
        WHERE g.year BETWEEN 1990 AND 2019
        GROUP BY g.year, g.month"
groundwater_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(groundwater_df)),
              bootstrap_options = c("striped", "hover"))
year month mean_value
350 2019 2 63735.69
351 2019 3 60792.13
352 2019 4 54698.44
353 2019 5 53237.05
354 2019 6 49160.99
355 2019 7 76941.00
boxplot(mean_value ~ year, groundwater_df, col=seaborn_palette[1:10], 
        main="Groundwater Well Depth Mean Values", cex.main=1.5)

sql <- "WITH sites AS (
            SELECT CONCAT(g.year, '-', g.month, '-1')::date AS \"date\",
                   g.site_name,
                   AVG(g.mean_value) as mean_value
            FROM groundwater g
            WHERE g.year BETWEEN 1990 AND 2019
            GROUP BY CONCAT(g.year, '-', g.month, '-1')::date,
                     g.site_name
        ), bottom AS (
            SELECT site_name, AVG(mean_value) AS mean_value, 'bottom_sites' AS category
            FROM sites 
            GROUP BY site_name
            ORDER BY AVG(mean_value) ASC LIMIT 5
        ), top AS (
            SELECT site_name, AVG(mean_value) AS mean_value, 'top_sites' AS category
            FROM sites 
            GROUP BY site_name
            ORDER BY AVG(mean_value) DESC LIMIT 5
        )
        SELECT sites.\"date\", sites.mean_value, sites.site_name, top.category
        FROM sites
        INNER JOIN top
            ON sites.site_name = top.site_name
        UNION
        SELECT sites.\"date\", sites.mean_value, sites.site_name, bottom.category
        FROM sites
        INNER JOIN bottom
            ON sites.site_name = bottom.site_name
        ORDER BY category, site_name, \"date\""
groundwater_df <- dbGetQuery(conn, sql)
kable_styling(kable(head(groundwater_df)),
              bootstrap_options = c("striped", "hover"))
date mean_value site_name category
1991-06-01 31 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
1991-07-01 35 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
1991-08-01 41 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
1991-09-01 42 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
1991-10-01 36 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
1991-11-01 40 FAIRPLAINS 2 WELL~ ST. CROIX~ USVI bottom_sites
groundwater_df$year <- format(groundwater_df$date, "%Y")
par(mfrow=c(5,2), mar=c(5, 5, 2, 1))
output <- by(groundwater_df, groundwater_df$site_name, function(sub) {
  
  metric_ts <- xts(x=sub[c("mean_value")], 
                   order.by=sub$date)
  
  print(plot(metric_ts, main = paste(sub$site_name[1], ':', sub$category[1]),
             legend.loc="bottomright", 
             col = seaborn_palette[1:3],
             yaxis.right=FALSE,
             axes=FALSE,
             lwd=1,
             cex.main=3,
             major.ticks="years",
             major.format="%Y",
             minor.format="%Y",
             grid.ticks.lty=1))
  
})

U.S. FWS Endangered Species List

sql <- "SELECT CONCAT((f.date_year/10)::int * 10, 's') AS decade, 
               f.taxonomic_group,
               f.current_status,
               count(*) AS species_count
        FROM fws_species_year f
        WHERE f.current_status IN ('Endangered', 'Recovery', 'Resolved Taxon', 
                                   'Threatened', 'Extinction')
        GROUP BY CONCAT((f.date_year/10)::int * 10, 's'),
                 f.taxonomic_group,
                 f.current_status
        ORDER BY CONCAT((f.date_year/10)::int * 10, 's'),
                 f.taxonomic_group,
                 f.current_status"
fws_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(fws_df)),
              bootstrap_options = c("striped", "hover"))
decade taxonomic_group current_status species_count
141 2010s Mammals Resolved Taxon 1
142 2010s Mammals Threatened 11
143 2010s Reptiles Endangered 4
144 2010s Reptiles Threatened 9
145 2010s Snails Endangered 18
146 2010s Snails Threatened 1
par(mfrow=c(5,3), mar=c(5, 5, 2, 1))
output <- by(fws_df, fws_df$taxonomic_group, function(sub) {
  plot_mat <- with(sub, tapply(species_count, list(decade, current_status), sum))
  
  barplot(plot_mat, main=sub$taxonomic_group[[1]], cex.main=1.5,
          ylim = c(0, max(plot_mat, na.rm=TRUE)+5),
          col=seaborn_palette[seq_along(row.names(plot_mat))], beside=TRUE)
  legend("top", row.names(plot_mat), fill=seaborn_palette[seq_along(row.names(plot_mat))], 
         ncol=length(row.names(plot_mat)))
  box()
})

U.S. Department of Agriculture: Agriculture Census

sql <- "SELECT year,
               CASE
                    WHEN domain_category = '' THEN 'TOTAL\nOPERATIONS'
                    ELSE REPLACE(REPLACE(REPLACE(domain_category, '(', '\n('), 'TO', 'TO\n'), 'OR', 'OR\n')
               END AS domain_category, 
               value
        FROM ag_census 
        WHERE data_item = 'FARM OPERATIONS - NUMBER OF OPERATIONS'"
agcensus_df <- dbGetQuery(conn, sql)
plot_mat <- with(agcensus_df, tapply(value, list(year, domain_category), sum))
par(mar=c(5, 5, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Number of Farm Operations", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 2E6+5E5), beside=TRUE, xaxt="n", yaxt="n")
axis(side=1, at=c(3,9,15,21,27,33,39,45)+0.5, labels=colnames(plot_mat), tick=FALSE, padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

sql <- "WITH sub AS (
          SELECT year,
                 data_item,
                 REPLACE(
                    REPLACE(
                        REPLACE(
                           REPLACE(
                              REPLACE(domain_category, ';', ','),
                              '(', '\n('), 
                           'TO', 'TO\n'),
                        ' OR', ' OR\n'),
                    'LESS', 'LESS\n') AS domain_category, 
                 value
          FROM ag_census 
          WHERE data_item LIKE '%COMMODITY TOTALS%'
            AND value IS NOT NULL
       ) 
       SELECT year, 
              CASE
                  WHEN TRIM(domain_category) = '' 
                  THEN CASE 
                            WHEN data_item = 'COMMODITY TOTALS - SALES; MEASURED IN $' 
                            THEN 'TOTAL\nSALES'
                            WHEN data_item = 'COMMODITY TOTALS - SALES; MEASURED IN $ / OPERATION' 
                            THEN 'TOTAL\nSALES\nPER OPERATION'
                       END
                  ELSE domain_category 
              END AS domain_category,
              value
       FROM sub
      "
agcensus_df <- within(dbGetQuery(conn, sql),
                      domain_category <- factor(domain_category,
                                                levels = c("FARM SALES: \n(LESS\n THAN 2,500 $)", "FARM SALES: \n(2,500 TO\n 4,999 $)", 
                                                           "FARM SALES: \n(5,000 TO\n 9,999 $)", "FARM SALES: \n(10,000 TO\n 24,999 $)",
                                                           "FARM SALES: \n(25,000 TO\n 49,999 $)", "FARM SALES: \n(50,000 TO\n 99,999 $)",
                                                           "FARM SALES: \n(100,000 TO\n 499,999 $)", "FARM SALES: \n(500,000 OR\n MORE $)", 
                                                           "TOTAL\nSALES", "TOTAL\nSALES\nPER OPERATION"))
)
kable_styling(kable(tail(agcensus_df)),
              bootstrap_options = c("striped", "hover"))
year domain_category value
45 2017 FARM SALES: (5,000 TO 9,999 $) 208074
46 2017 FARM SALES: (10,000 TO 24,999 $) 228218
47 2017 FARM SALES: (25,000 TO 49,999 $) 144113
48 2017 FARM SALES: (50,000 TO 99,999 $) 119434
49 2017 FARM SALES: (100,000 TO 499,999 $) 218771
50 2017 FARM SALES: (500,000 OR MORE $) 146568
plot_mat <- with(agcensus_df[agcensus_df$domain_category != 'TOTAL\nSALES',], 
                 tapply(value, list(year, factor(domain_category)), sum))
par(mar=c(5, 5, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Commodity Totals - Farm Sales", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 1E6), beside=TRUE, xaxt="n", yaxt="n")
axis(side=1, at=c(3,9,15,21,27,33,39,45,51)+0.5, labels=colnames(plot_mat), tick=FALSE, padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

plot_mat <- with(agcensus_df[agcensus_df$domain_category == 'TOTAL\nSALES',], 
                 tapply(value, list(year, factor(domain_category)), sum))
par(mar=c(5, 8, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Commodity Totals - Overall Farm Sales", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 4E11+5E10), beside=TRUE, xaxt="n", yaxt="n", space=2)
        
axis(side=1, at=c(2.5,5.5,8.5,11.5,14.5), labels=row.names(plot_mat), padj=0.9, pos=c(0,0))
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

sql <- "SELECT year, 
               REPLACE(REPLACE(data_item, ' - OPERATIONS WITH AREA HARVESTED', ''), '; GRAIN', '') as crop,
               value
        FROM ag_census
        WHERE data_item IN ('CORN; GRAIN - OPERATIONS WITH AREA HARVESTED',
                            'WHEAT - OPERATIONS WITH AREA HARVESTED',
                            'SOYBEANS - OPERATIONS WITH AREA HARVESTED')
        ORDER BY year, data_item
       "
crops_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(crops_df)),
              bootstrap_options = c("striped", "hover"))
year crop value
10 2012 CORN 348530
11 2012 SOYBEANS 302963
12 2012 WHEAT 147632
13 2017 CORN 304801
14 2017 SOYBEANS 303191
15 2017 WHEAT 104792
plot_mat <- with(crops_df, tapply(value, 
                                  list(year, factor(crop, levels=c("WHEAT", "SOYBEANS", "CORN"))), sum)
)
par(mar=c(5, 8, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Number of Operations by Specific Crops", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 5E5), yaxt="n", beside=TRUE)
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

sql <- "SELECT year, 
               REPLACE(
                  REPLACE(
                     REPLACE(data_item, ' - OPERATIONS WITH SALES', ''), 
                     '; INCL CALVES', ''),
                  '; BROILERS', '') as livestock,
               value
        FROM ag_census
        WHERE data_item IN ('CATTLE; INCL CALVES - OPERATIONS WITH SALES',
                            'HOGS - OPERATIONS WITH SALES',
                            'CHICKENS; BROILERS - OPERATIONS WITH SALES')
        ORDER BY year, data_item
       "
livestock_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(livestock_df)),
              bootstrap_options = c("striped", "hover"))
year livestock value
10 2012 CATTLE 740978
11 2012 CHICKENS 32935
12 2012 HOGS 55882
13 2017 CATTLE 711827
14 2017 CHICKENS 32751
15 2017 HOGS 64871
plot_mat <- with(livestock_df, tapply(value, 
                                  list(year, factor(livestock, levels=c( "CHICKENS", "HOGS", "CATTLE"))), sum)
)
par(mar=c(5, 8, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Number of Operations by Specific Livestock", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 1E6+2E5), yaxt="n", beside=TRUE)
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

sql <- "SELECT year, 
               CASE
                   WHEN data_item = 'CROP TOTALS - SALES; MEASURED IN $' THEN 'CROP TOTALS'
                   WHEN data_item = 'ANIMAL TOTALS; INCL PRODUCTS - SALES; MEASURED IN $' THEN 'ANIMAL TOTALS'
               END as category,
               value
        FROM ag_census
        WHERE data_item IN ('CROP TOTALS - SALES; MEASURED IN $',
                            'ANIMAL TOTALS; INCL PRODUCTS - SALES; MEASURED IN $')
        ORDER BY year, data_item
       "
agtotal_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(agtotal_df)),
              bootstrap_options = c("striped", "hover"))
year category value
5 2007 ANIMAL TOTALS 153562563000
6 2007 CROP TOTALS 143657928000
7 2012 ANIMAL TOTALS 182247407000
8 2012 CROP TOTALS 212397074000
9 2017 ANIMAL TOTALS 194975996000
10 2017 CROP TOTALS 193546699000
plot_mat <- with(agtotal_df, tapply(value, list(year, category), sum))
par(mar=c(5, 8, 2, 1))
barplot(plot_mat, main="U.S. Agriculture Census: Crop vs Animal Sales", cex.main=1.5,
        col=seaborn_palette[1:5], ylim=c(0, 2E11+5E10), yaxt="n", beside=TRUE)
axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE), las=1)
legend("top", row.names(plot_mat), fill=seaborn_palette[1:5], ncol=5)

World Metrics

sql <- "WITH pop AS 
            (SELECT p.year,
                    p.population
             FROM world_population p
             WHERE p.country_name = 'World'
               AND p.year BETWEEN 2000 AND 2019
             ),
              
              land AS
            (SELECT a.year,
                    a.percent_arable
             FROM arable_land a
             WHERE a.country_name = 'World'
               AND a.year BETWEEN 2000 AND 2019
             ),
         
              fauna AS
            (SELECT i.year,
                    SUM(i.species_count) AS animals_count
             FROM iucn_species_count i
             WHERE i.year BETWEEN 2000 AND 2019
             GROUP BY i.year
             ),
         
              flora AS
            (SELECT p.assessment_year as year,
                    COUNT(*) AS plants_count
             FROM plants_assessments p
             WHERE p.assessment_year BETWEEN 2000 AND 2019
               AND p.interpreted_status = 'Threatened'
             GROUP BY p.assessment_year
             ),             
         
              ice AS
            (SELECT s.date_year as year,
                    AVG(s.extent) FILTER(WHERE s.region = 'Arctic') AS arctic_sea_ice_extent,
                    AVG(s.extent) FILTER(WHERE s.region = 'Antarctica') AS antarctic_sea_ice_extent
             FROM sea_ice_extent s
             WHERE s.date_year BETWEEN 2000 AND 2019
             GROUP BY s.date_year
             ),
             
              ocean AS
            (SELECT o.year as year,
                    AVG(o.tco2) AS total_co2,
                    AVG(o.phts25p0) AS ph_scale
             FROM ocean_data o
             WHERE o.year BETWEEN 2000 AND 2019
               AND o.tco2 <> -9999 AND o.phts25p0 <> -9999
             GROUP BY o.year
             ),
             
              temp AS
            (SELECT g.year as year,
                    AVG(g.global_mean) AS global_mean
             FROM global_temperature g
             WHERE g.year BETWEEN 2000 AND 2019
             GROUP BY g.year
             )
             
         SELECT pop.year, pop.population, land.percent_arable, fauna.animals_count, flora.plants_count,
                ice.arctic_sea_ice_extent, ice.antarctic_sea_ice_extent, 
                ocean.total_co2, ocean.ph_scale, temp.global_mean
         FROM pop 
         JOIN land USING (year)
         JOIN fauna USING (year)
         JOIN flora USING (year)
         JOIN ice USING (year)
         JOIN ocean USING (year)
         JOIN temp USING (year)
         ORDER BY pop.year"
env_world_df <- dbGetQuery(conn, sql)
kable_styling(kable(tail(env_world_df)), font_size = 12,
              bootstrap_options = c("striped", "hover"))
year population percent_arable animals_count plants_count arctic_sea_ice_extent antarctic_sea_ice_extent total_co2 ph_scale global_mean
7 2008 6765986891 10.80640 16928 3279 10.97785 12.23941 2181.326 7.685694 0.5155556
8 2009 6849272706 10.77478 17291 2294 10.93197 12.04859 2176.104 7.735574 0.6294444
9 2010 6932596129 10.74432 18351 3217 10.71139 12.10679 2191.072 7.702093 0.7022222
10 2011 7014792135 10.80189 19570 2850 10.48350 11.50057 2224.690 7.675097 0.5844444
11 2012 7099311892 10.87358 20219 7556 10.40610 12.00444 2187.112 7.731700 0.6183333
12 2013 7184861447 10.89469 21353 5465 10.89712 12.52361 2183.400 7.723483 0.6438889
kable_styling(kable(cor(env_world_df[-1])), font_size = 12,
              bootstrap_options = c("striped", "hover"))
population percent_arable animals_count plants_count arctic_sea_ice_extent antarctic_sea_ice_extent total_co2 ph_scale global_mean
population 1.0000000 0.1656147 0.9774118 0.7453433 -0.8026257 0.5047082 0.5832658 -0.1705909 0.5630321
percent_arable 0.1656147 1.0000000 0.2441270 0.4917138 0.0689147 0.3901610 -0.0431136 0.1567368 -0.3399306
animals_count 0.9774118 0.2441270 1.0000000 0.7450838 -0.8024194 0.5181381 0.6740957 -0.2912376 0.4783654
plants_count 0.7453433 0.4917138 0.7450838 1.0000000 -0.7224389 0.3576609 0.3673134 -0.1134181 0.3630865
arctic_sea_ice_extent -0.8026257 0.0689147 -0.8024194 -0.7224389 1.0000000 -0.0588615 -0.7926131 0.5220906 -0.5461557
antarctic_sea_ice_extent 0.5047082 0.3901610 0.5181381 0.3576609 -0.0588615 1.0000000 0.1042700 0.0767117 0.0713994
total_co2 0.5832658 -0.0431136 0.6740957 0.3673134 -0.7926131 0.1042700 1.0000000 -0.8165837 0.2653096
ph_scale -0.1705909 0.1567368 -0.2912376 -0.1134181 0.5220906 0.0767117 -0.8165837 1.0000000 0.0463203
global_mean 0.5630321 -0.3399306 0.4783654 0.3630865 -0.5461557 0.0713994 0.2653096 0.0463203 1.0000000
par(mfrow=c(4,2), mai = c(0.3, 0.2, 0.7, 0.2))
for(x in colnames(env_world_df)[3:ncol(env_world_df)]) {
  lfit <- loess(paste(x, "~ population"), data=env_world_df)
  
  plot(as.formula(paste(x, "~ population")), env_world_df, 
       main=paste("population and\n", gsub("_", " ", x)), cex.main=2,
       type="p", col=seaborn_palette[1], yaxt='n', xaxt='n', pch=19)
  axis(side=1, at=axTicks(1), labels=format(axTicks(1), big.mark=',', scientific=FALSE))
  axis(side=2, at=axTicks(2), labels=format(axTicks(2), big.mark=',', scientific=FALSE))
  pop_order <- order(env_world_df$population)
  lines(env_world_df$population[pop_order], lfit$fitted[pop_order], col=seaborn_palette[4], lwd=3)
}

dbDisconnect(conn)
[1] TRUE

User Data Application

# LINUX SHELL COMMAND CALL
system(paste0("gnome-terminal -- Rscript -e \"library(shiny); setwd('", getwd(), "'); runApp('EnvironmentDB_Shiny_App.R')\""))

Conclusion




  • Postgres as tool in data science workflow

  • Data persistence and hygiene

  • Centralization and security

  • Efficiency and usefulness

  • Scalability and applicability



LS0tCnRpdGxlOiAiUG9zdGdyZVNRTCBBcyBEYXRhIFNjaWVuY2UgRGF0YWJhc2UiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Ci5tYWluLWNvbnRhaW5lciB7CiAgbWF4LXdpZHRoOiAxMDAwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9Cjwvc3R5bGU+Cgo8YnIvPgo8ZGl2IHN0eWxlPSJmb250LXNpemU6IDIwcHg7Ij5DaGljYWdvIFBvc3RncmVzIFVzZXIgR3JvdXAgTWVldHVwLCBKYW51YXJ5IDEzLCAyMDIwPC9kaXY+CjxkaXYgc3R5bGU9ImZsb2F0OmxlZnQiPjxpbWcgc3JjPSJJTUFHRVMvcG9zdGdyZXNxbF9yLnBuZyIgd2lkdGg9IjIwMHB4Ii8+PC9kaXY+Cjxici8+PGJyLz48YnIvPjxici8+CgojIyBQYXJmYWl0IEdhc2FuYSAjIwo8ZGl2IHN0eWxlPSJmb250LXNpemU6IDIwcHg7Ij5EYXRhIEFuYWx5c3QsIFdpbnN0b24gJiBTdHJhd248L2Rpdj4KPGRpdiBzdHlsZT0iZmxvYXQ6bGVmdCI+PGltZyBzcmM9IklNQUdFUy9zdGFja292ZXJmbG93LnBuZyIgd2lkdGg9IjMwcHgiLz48L2Rpdj4KPGRpdiBzdHlsZT0iZm9udC1zaXplOiAxNnB4OyI+QFBhcmZhaXQgKFN0YWNrT3ZlcmZsb3cpIHwgCjxpbWcgc3JjPSJJTUFHRVMvZ2l0aHViLnBuZyIgd2lkdGg9IjMwcHgiLz4mbmJzcDsmbmJzcDtAUGFyZmFpdEcgKEdpdEh1Yik8L2Rpdj4KCjxociBzdHlsZT0iYm9yZGVyOiBub25lOyBoZWlnaHQ6IDFweDsgYmFja2dyb3VuZC1jb2xvcjogI0NDQzsiLz4KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQocm9vdC5kaXIgPSAiL2hvbWUvcGFyZmFpdGcvRG9jdW1lbnRzL1BHU1FMIikKYGBgCgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGltZyBzcmM9IklNQUdFUy9vcGVuX3NvdXJjZS5wbmciIHdpZHRoPSI3NXB4Ii8+PC9kaXY+Cjxici8+CgojIyBPcGVuIFNvdXJjZSBTdWNjZXNzIFN0b3JpZXMKCjxici8+Cgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGltZyBzcmM9IklNQUdFUy9wb3N0Z3Jlc3FsLmpwZWciIHdpZHRoPSI1MHB4Ii8+PC9kaXY+Ci0gIyMjIFBvc3RncmVTUUw6IHBvd2VyZnVsLCBleHRlbnNpYmxlLCBhbmFseXRpY2FsIFJEQk1TCiAgICAtICMjIyMgW0NvbW11bml0eV0oaHR0cHM6Ly93d3cucG9zdGdyZXNxbC5vcmcvY29tbXVuaXR5Lyk6IGNvbnRyaWJ1dG9ycywgbWFpbGluZyBsaXN0cywgbG9jYWwgdXNlciBncm91cHMKPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQiPjxpbWcgc3JjPSJJTUFHRVMvcl9sb2dvLnBuZyIgd2lkdGg9IjUwcHgiLz48L2Rpdj4KLSAjIyMgUjogZGF0YSBzY2llbmNlIHN0YWNrLCBleHRlbnNpYmxlIGVudmlyb25tZW50CiAgICAtICMjIyMgW0NSQU5dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykgcmVwb3NpdG9yeSBvZiBwYWNrYWdlcwo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGltZyBzcmM9IklNQUdFUy9weXRob24ucG5nIiB3aWR0aD0iNTBweCIvPjwvZGl2PgotICMjIyBQeXRob246IGdlbmVyYWwgcHVycG9zZSwgZXh0ZW5zaWJsZSBsYW5ndWFnZQogICAgLSAjIyMjIFtQeVBJXShodHRwczovL3B5cGkub3JnLykgZWNvc3lzdGVtIG9mIG1vZHVsZXMKCjxkaXYgc3R5bGU9Im1hcmdpbi1sZWZ0OjQwcHg7Ii8+PGgzPkNoYWxsZW5naW5nIENvbW1lcmNpYWwgTWFya2V0IFNoYXJlPC9oMz48L2Rpdj4KPGRpdiBzdHlsZT0iZmxvYXQ6bGVmdDsgbWFyZ2luLWxlZnQ6MTAwcHg7Ii8+PGltZyBzcmM9IklNQUdFUy9jb21tZXJjaWFsX3Rvb2xzLnBuZyIgd2lkdGg9IjIwMHB4Ii8+PC9kaXY+Cjxici8+Cjxici8+Cjxici8+Cjxici8+Cgo8aHIgc3R5bGU9ImJvcmRlcjogbm9uZTsgaGVpZ2h0OiAxcHg7IGJhY2tncm91bmQtY29sb3I6ICNDQ0M7Ii8+Cgo8YnIvPgoKPGgyPjxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPlJlbGF0aW9uYWwgRGF0YWJhc2UgU29sdXRpb248L3NwYW4+PC9oMj4KCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0OyI+PGltZyBzcmM9IklNQUdFUy9SREJNU19Hcm91cC5wbmciIHdpZHRoPSIyNTBweCIvPjwvZGl2PgoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+RGF0YSBwZXJzaXN0ZW5jZTogaGlzdG9yaWNhbCBhbmQgY3VycmVudCBuZWVkczwvc3Bhbj4KLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+TWFpbnRlbmFuY2UvaHlnaWVuZTogYWRoZXJlbmNlIHRvIHR5cGVzPC9zcGFuPgoKPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQ7Ij48aW1nIHNyYz0iSU1BR0VTL3Bvc3RncmVzcWwuanBlZyIgd2lkdGg9IjEwMHB4Ii8+PC9kaXY+CgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICMzMzY3OTEiLz5TdG9yYWdlIGVmZmljaWVuY3k6IG5vcm1hbGl6YXRpb24gcmVkdWNlcyByZXBldGl0aW9uIG9mIGRhdGE8L3NwYW4+Ci0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPkNlbnRyYWxpemF0aW9uOiBtdWx0aXBsZSB1c2VyIGVudmlyb25tZW50IGFuZCBzZWN1cml0eTwvc3Bhbj4KLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+U2NhbGFiaWxpdHk6IG5vdCBsaW1pdGVkIHRvIGxvY2FsIHJlc291cmNlczwvc3Bhbj4KCjxociBzdHlsZT0iYm9yZGVyOiBub25lOyBoZWlnaHQ6IDFweDsgYmFja2dyb3VuZC1jb2xvcjogI0NDQzsiLz4KCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48aW1nIHNyYz0iSU1BR0VTL2RvY2tlcl9wZ3NxbC5wbmciIHdpZHRoPSIxNTBweCIvPjwvZGl2PgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICMzMzY3OTEiLz5Vc2UgQ2FzZTogRW52aXJvbm1lbnQgRGF0YWJhc2U8L2E+PC9zcGFuPgoKPGgzPjxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPjxpPkh1bWFuIEltcGFjdCBvbiB0aGUgR2xvYmFsIGFuZCBMb2NhbCBCaW9zcGhlcmUgYW5kIENsaW1hdGU8L2k+PC9zcGFuPjwvaDM+CjxkaXYgc3R5bGU9ImZsb2F0OmNlbnRlcjsgdGV4dC1hbGlnbjpjZW50ZXI7Ij48aW1nIHNyYz0iSU1BR0VTL2Vudl9kYXRhLnBuZyIvPjwvZGl2Pgo8YnIvPgoKPGhyIHN0eWxlPSJib3JkZXI6IG5vbmU7IGhlaWdodDogMXB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjQ0NDOyIvPgoKYGBge3J9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShrYWJsZUV4dHJhKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHh0cykpCgpzZWFib3JuX3BhbGV0dGUgPC0gYygiIzRDNzJCMCIsICIjREQ4NDUyIiwgIiM1NUE4NjgiLCAiI0M0NEU1MiIsICIjODE3MkIzIiwgIiM5Mzc4NjAiLCAKICAgICAgICAgICAgICAgICAgICAgIiNEQThCQzMiLCAiIzhDOEM4QyIsICIjQ0NCOTc0IiwgIiM2NEI1Q0QiLCAiIzRDNzJCMCIsICIjREQ4NDUyIikKYGBgCgojIyBDb25uZWN0aW9uCgotICMjIyBDZW50cmFsaXplZCBsb2NhdGlvbiBmb3IgbXVsdGlwbGUgdXNlcnMKLSAjIyMgV2VsbCBtYWludGFpbmVkIEFQSSBtb2R1bGVzCi0gIyMjIE11bHRpcGxlIGJhY2tlbmQgY29ubmVjdGlvbiB0eXBlcwoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPjxhIGhyZWY9Imh0dHBzOi8vd3d3LnBvc3RncmVzcWwub3JnL2Z0cC9vZGJjL3ZlcnNpb25zL3NyYy8iIHRhcmdldD0iYmxhbmsiPk9EQkM8L2E+PC9zcGFuPgoKYGBge3J9CmxpYnJhcnkoREJJKQpsaWJyYXJ5KG9kYmMpCgpjb25uIDwtIGRiQ29ubmVjdChvZGJjOjpvZGJjKCksIGRyaXZlcj0iUG9zdGdyZVNRTCBVbmljb2RlIiwgCiAgICAgICAgICAgICAgICAgIHNlcnZlcj0ibG9jYWxob3N0IiwgZGF0YWJhc2U9ImVudmlyb25tZW50IiwKICAgICAgICAgICAgICAgICAgdWlkPSJwb3N0Z3JlcyIsIHB3ZD0iZW52MTkiLCBwb3J0PTY0MzIpCmRiTGlzdFRhYmxlcyhjb25uKQoKZGJEaXNjb25uZWN0KGNvbm4pCmBgYAoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPjxhIGhyZWY9Imh0dHBzOi8vamRiYy5wb3N0Z3Jlc3FsLm9yZy9kb3dubG9hZC5odG1sIiB0YXJnZXQ9ImJsYW5rIj5KREJDPC9hPjwvc3Bhbj4KCmBgYHtyfQpsaWJyYXJ5KHJKYXZhKQpsaWJyYXJ5KFJKREJDKQoKZHJ2IDwtIEpEQkMoIm9yZy5wb3N0Z3Jlc3FsLkRyaXZlciIsCiAgICAgICAgICAgIi91c3IvbGliL2p2bS9qYXZhLTgtb3JhY2xlL2xpYi9wb3N0Z3Jlc3FsLTQyLjIuMi5qYXIiKQpjb25uIDwtIGRiQ29ubmVjdChkcnYsICJqZGJjOnBvc3RncmVzcWw6Ly9sb2NhbGhvc3Q6NjQzMi9lbnZpcm9ubWVudCIsICJwb3N0Z3JlcyIsICJlbnYxOSIpCmRiTGlzdFRhYmxlcyhjb25uLCBzY2hlbWE9InB1YmxpYyIpCgpkYkRpc2Nvbm5lY3QoY29ubikKYGBgCgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPjxhIGhyZWY9Imh0dHBzOi8vcmRyci5pby9jcmFuL1JQb3N0Z3JlU1FMLyIgdGFyZ2V0PSJibGFuayI+Ui1Qb3N0Z3JlcyBBUEk8L2E+PC9zcGFuPgoKYGBge3J9CmxpYnJhcnkoUlBvc3RncmVTUUwpCgpjb25uIDwtIGRiQ29ubmVjdChSUG9zdGdyZVNRTDo6UG9zdGdyZVNRTCgpLCBob3N0PSJsb2NhbGhvc3QiLCBkYm5hbWU9ImVudmlyb25tZW50IiwKICAgICAgICAgICAgICAgICAgdXNlcj0icG9zdGdyZXMiLCBwYXNzd29yZD0iZW52MTkiLCBwb3J0PTY0MzIpCmRiTGlzdFRhYmxlcyhjb25uKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOiBub25lOyBoZWlnaHQ6IDFweDsgYmFja2dyb3VuZC1jb2xvcjogI0NDQzsiLz4KCiMjIEltcG9ydCAmIE1hbmFnZSBEYXRhCgotICMjIyMgU2VhbWxlc3MgYnVsayB0ZXh0IGZpbGUgaW1wb3J0Ci0gIyMjIyBEYXRhIGh5Z2llbmUgc3VwcG9ydAotICMjIyMgQW5ub3RhdGUgc291cmNlcyB3aXRoIGNvbW1lbnRzCgpgYGB7c3FsLCBldmFsPUZBTFNFfQpDUkVBVEUgVEFCTEUgZ2xvYmFsX3RlbXBlcmF0dXJlICgKICAgaWQgU0VSSUFMIE5PVCBOVUxMIFBSSU1BUlkgS0VZLAogICB5ZWFyIElOVCwKICAgcGVyaW9kIFZBUkNIQVIoNTApLAogICBnbG9iYWxfbWVhbiBOVU1FUklDKDUsMikKKTsKClxjb3B5IGdsb2JhbF90ZW1wZXJhdHVyZSAoeWVhciwgcGVyaW9kLCBnbG9iYWxfbWVhbikgRlJPTSAnL2hvbWUvcGFyZmFpdGcvRGF0YWJhc2VzL1NRTF9TZXJ2ZXIvRU5WSVJPTk1FTlQvZ2xvYmFsX3RlbXBlcmF0dXJlLmNzdicgREVMSU1JVEVSICcsJyBDU1YgSEVBREVSOwoKQ09NTUVOVCBPTiBUQUJMRSBnbG9iYWxfdGVtcGVyYXR1cmUgSVMgJ1NvdXJjZTogTkFTQSAtIENvbWJpbmVkIExhbmQtU3VyZmFjZSBBaXIgYW5kIFNlYS1TdXJmYWNlIFdhdGVyIFRlbXBlcmF0dXJlIEFub21hbGllcyAoTGFuZC1PY2VhbiBUZW1wZXJhdHVyZSBJbmRleCwgTE9USSkgKGh0dHBzOi8vZGF0YS5naXNzLm5hc2EuZ292L2dpc3RlbXAvKSc7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbm4sIG91dHB1dC52YXIgPSAiY29tbWVudHNfZGYifQpTRUxFQ1QgcmVsbmFtZSBhcyB0YWJsZSwgb2JqX2Rlc2NyaXB0aW9uKG9pZCkgYXMgY29tbWVudApGUk9NIHBnX2NsYXNzCldIRVJFIHJlbGtpbmQgPSAncicKICBBTkQgb2JqX2Rlc2NyaXB0aW9uKG9pZCkgSVMgTk9UIE5VTEwKICBPUkRFUiBCWSByZWxuYW1lCmBgYAoKYGBge3J9CmthYmxlX3N0eWxpbmcoa2FibGUoY29tbWVudHNfZGYpLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCjxociBzdHlsZT0iYm9yZGVyOiBub25lOyBoZWlnaHQ6IDFweDsgYmFja2dyb3VuZC1jb2xvcjogI0NDQzsiLz4KCiMjIEFuYWx5emUgRGF0YQoKLSAjIyMjIERlY2FsYXJhdGl2ZSBTUUwgdG8gaW50ZXJhY3Qgd2l0aCBtYW55IGRhdGEKLSAjIyMjIExldmVyYWdlIHF1ZXJ5IGVuZ2luZSBmb3IgZGF0YSBwcm9jZXNzaW5nCi0gIyMjIyBSZWFkYWJsZSwgbWFpbnRhaW5hYmxlLCB0cmFuZmVyYWJsZQoKPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQiPjxhIGhyZWY9Imh0dHBzOi8vd3d3LmVzcmwubm9hYS5nb3YvZ21kL29ib3AvbWxvLyIgYmxhbms9InRhcmdldCI+PGltZyBzcmM9IklNQUdFUy9ub2FhLnBuZyIgaGVpZ2h0PSI1MHB4Ii8+PC9hPjwvZGl2Pgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGEgaHJlZj0iaHR0cHM6Ly93d3cuZWlhLmdvdi90b3RhbGVuZXJneS9kYXRhL21vbnRobHkvIiBibGFuayA9InRhZ2V0Ij48aW1nIHNyYz0iSU1BR0VTL2RvZV9laWEucG5nIiBoZWlnaHQ9IjUwcHgiLz48L2E+PC9kaXY+CgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPkRlcGFydG1lbnQgb2YgRW5lcmd5J3MgRUlBOiBNb250aGx5IEVuZXJneSBSZXZpZXcgJjwvc3Bhbj4KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPk5PQUEgTWF1bmEgTG9hLCBIYXdhaWkgT2JzZXJ2YXRvcnkgLSBDYXJib24gUFBNPC9zcGFuPgoKYGBge3NxbCBjb25uZWN0aW9uPWNvbm4sIG91dHB1dC52YXIgPSAibWV0cmljc19kZiJ9ClNFTEVDVCBwLmRhdGVfeWVhciwgcC5kYXRlX21vbnRoLCBDT05DQVQocC5kYXRlX3llYXIsICctJywgcC5kYXRlX21vbnRoLCAnLTAxJyk6OmRhdGUgYXMgZGF0ZV9kYXksCiAgICAgICAgICAgICAgIHAuYXZlcmFnZV9wcG0gYXMgImNhcmJvbiBwcG0iLCBjLmVuZXJneV9jb25zdW1lZCBhcyAiYnR1IGNvbnN1bWVkIiwgZS5lbmVyZ3lfY28yIGFzICJjbzIgZW1pc3Npb25zIgogIEZST00gcHBtX21vbnRoIHAKICBKT0lOIGNvbnN1bXB0aW9uIGMgCiAgICBPTiBwLmRhdGVfeWVhciA9IGMuZGF0ZV95ZWFyIEFORCBwLmRhdGVfbW9udGggPSBjLmRhdGVfbW9udGggYW5kIGMubXNuID0gJ1RYUkNCVVMnCiAgSk9JTiB1c19jbzJfZW1pc3Npb25zIGUKICAgIE9OIHAuZGF0ZV95ZWFyID0gZS5kYXRlX3llYXIgQU5EIHAuZGF0ZV9tb250aCA9IGUuZGF0ZV9tb250aCBhbmQgZS5tc24gPSAnVEVUQ0VVUycKICBPUkRFUiBCWSBwLmRhdGVfeWVhciwgcC5kYXRlX21vbnRoCmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbm4sIG91dHB1dC52YXIgPSAiYWdnX2RmIn0KU0VMRUNUIHAuZGF0ZV95ZWFyLAogICAgICAgc3VtKHAuYXZlcmFnZV9wcG0pIGFzIGNhcmJvbl9wcG1fdG90YWwsIAogICAgICAgYXZnKHAuYXZlcmFnZV9wcG0pIGFzIGNhcmJvbl9wcG1fbWVhbiwgCiAgICAgICBzdW0oYy5lbmVyZ3lfY29uc3VtZWQpIGFzIGJ0dV9jb25zdW1lZF90b3RhbCwKICAgICAgIGF2ZyhjLmVuZXJneV9jb25zdW1lZCkgYXMgYnR1X2NvbnN1bWVkX21lYW4sCiAgICAgICBzdW0oZS5lbmVyZ3lfY28yKSBhcyBjbzJfZW1pc3Npb25zX3RvdGFsLAogICAgICAgYXZnKGUuZW5lcmd5X2NvMikgYXMgY28yX2VtaXNzaW9uc19tZWFuCiAgRlJPTSBwcG1fbW9udGggcAogIEpPSU4gY29uc3VtcHRpb24gYyAKICAgIE9OIHAuZGF0ZV95ZWFyID0gYy5kYXRlX3llYXIgQU5EIHAuZGF0ZV9tb250aCA9IGMuZGF0ZV9tb250aCBhbmQgYy5tc24gPSAnVFhSQ0JVUycKICBKT0lOIHVzX2NvMl9lbWlzc2lvbnMgZQogICAgT04gcC5kYXRlX3llYXIgPSBlLmRhdGVfeWVhciBBTkQgcC5kYXRlX21vbnRoID0gZS5kYXRlX21vbnRoIGFuZCBlLm1zbiA9ICdURVRDRVVTJwpHUk9VUCBCWSBwLmRhdGVfeWVhcgpPUkRFUiBCWSBwLmRhdGVfeWVhcgpgYGAKCmBgYHtyfQprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwoYWdnX2RmLCAxMCkpLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCmBgYHtyIGZpZzFhLCBmaWcuaGVpZ2h0ID0gNywgZmlnLndpZHRoID0gMTMsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQptZXRyaWNfdHMgPC0geHRzKHg9bWV0cmljc19kZltjKCJjYXJib24gcHBtIiwgImJ0dSBjb25zdW1lZCIsICJjbzIgZW1pc3Npb25zIildLCAKICAgICAgICAgICAgICAgICBvcmRlci5ieT1tZXRyaWNzX2RmJGRhdGVfZGF5KQoKcGxvdChtZXRyaWNfdHMsIG1haW4gPSAiRW5lcmd5IGFuZCBDYXJib24gUFBNIE1ldHJpY3MiLAogICAgIGxlZ2VuZC5sb2M9ImJvdHRvbXJpZ2h0IiwgCiAgICAgY29sID0gc2VhYm9ybl9wYWxldHRlWzE6M10sCiAgICAgeWF4aXMucmlnaHQ9RkFMU0UsCiAgICAgYXhlcz1GQUxTRSwKICAgICBsd2Q9MSwKICAgICBjZXgubWFpbj0zLAogICAgIG1ham9yLnRpY2tzPSJ5ZWFycyIsCiAgICAgbWFqb3IuZm9ybWF0PSIlWSIsCiAgICAgbWlub3IuZm9ybWF0PSIlWSIsCiAgICAgZ3JpZC50aWNrcy5sdHk9MSkKYGBgCgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+U2Vhc29uYWwgRGVjb21wb3NpdGlvbjwvc3Bhbj4KCmBgYHtyICBmaWcxYiwgZmlnLmhlaWdodCA9IDcsIGZpZy53aWR0aCA9IDEzLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KY2FyYm9ucHBtX3RzIDwtIHRzKG1ldHJpY3NfZGYkYGNhcmJvbiBwcG1gLCBzdGFydD1jKDE5NzMsIDEpLCBmcmVxdWVuY3k9MTIpCmNhcmJvbnBwbV9zdGwgPC0gc3RsKGNhcmJvbnBwbV90cywgcy53aW5kb3c9InBlcmlvZGljIikKCnBsb3QoY2FyYm9ucHBtX3N0bCwgY29sID0gc2VhYm9ybl9wYWxldHRlWzFdLCAKICAgICBtYWluPSJTZWFzb25hbCBEZWNvbXBvc2l0aW9uIG9mIEdsb2JhbCBDYXJib24gUFBNIikKYGBgCgpgYGB7ciAgZmlnMWMsIGZpZy5oZWlnaHQgPSA3LCBmaWcud2lkdGggPSAxMywgZmlnLmFsaWduID0gImNlbnRlciJ9CmNvbnN1bWVkX3RzIDwtIHRzKG1ldHJpY3NfZGYkYGJ0dSBjb25zdW1lZGAsIHN0YXJ0PWMoMTk3MywgMSksIGZyZXF1ZW5jeT0xMikKY29uc3VtZWRfc3RsIDwtIHN0bChjb25zdW1lZF90cywgcy53aW5kb3c9InBlcmlvZGljIikKCnBsb3QoY29uc3VtZWRfc3RsLCBjb2wgPSBzZWFib3JuX3BhbGV0dGVbMl0sIAogICAgIG1haW49IlNlYXNvbmFsIERlY29tcG9zaXRpb24gb2YgVS5TLiBFbmVyZ3kgQ29uc3VtcHRpb24iKQpgYGAKCmBgYHtyIGZpZzFkLCBmaWcuaGVpZ2h0ID0gNywgZmlnLndpZHRoID0gMTMsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQplbWlzc2lvbnNfdHMgPC0gdHMobWV0cmljc19kZiRgY28yIGVtaXNzaW9uc2AsIHN0YXJ0PWMoMTk3MywgMSksIGZyZXF1ZW5jeT0xMikKZW1pc3Npb25zX3N0bCA8LSBzdGwoZW1pc3Npb25zX3RzLCBzLndpbmRvdz0icGVyaW9kaWMiKQoKcGxvdChlbWlzc2lvbnNfc3RsLCBjb2wgPSBzZWFib3JuX3BhbGV0dGVbM10sCiAgICAgbWFpbiA9ICJTZWFzb25hbCBEZWNvbXBvc2l0aW9uIG9mIFUuUy4gQ08yIEVtaXNzaW9ucyIpCmBgYAoKYGBge3J9CnNxbCA8LSAiV0lUSCBjMSBBUwogICAgICAgICAgIChTRUxFQ1QgQ09OQ0FUKChkYXRlX3llYXIvMTApOjppbnQgKiAxMCwgJ3MnKSBhcyBkZWNhZGUsIAogICAgICAgICAgICAgICAgICAgUkVQTEFDRShkZXNjcmlwdGlvbiwgJ1ByaW1hcnkgRW5lcmd5IENvbnN1bWVkIGJ5IHRoZSAnLCAnJykgQVMgXCJTZWN0b3JcIiwKICAgICAgICAgICAgICAgICAgIFNVTShlbmVyZ3lfY29uc3VtZWQpIEFTIFwiUHJpbWFyeSBFbmVyZ3kgQ29uc3VtZWRcIgogICAgICAgICAgICBGUk9NIGNvbnN1bXB0aW9uCiAgICAgICAgICAgIFdIRVJFIG1zbiBJTiAoJ1RYSUNCVVMnLCAnVFhSQ0JVUycsICdUWEFDQlVTJywgJ1RYQ0NCVVMnLCAnVFhFSUJVUycpCiAgICAgICAgICAgIEdST1VQIEJZIENPTkNBVCgoZGF0ZV95ZWFyLzEwKTo6aW50ICogMTAsICdzJyksIGRlc2NyaXB0aW9uCiAgICAgICAgICAgKSwgYzIgQVMKICAgICAgICAgICAoU0VMRUNUIENPTkNBVCgoZGF0ZV95ZWFyLzEwKTo6aW50ICogMTAsICdzJykgYXMgZGVjYWRlLCAKICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoZGVzY3JpcHRpb24sICdUb3RhbCBFbmVyZ3kgQ29uc3VtZWQgYnkgdGhlICcsICcnKSBBUyBcIlNlY3RvclwiLAogICAgICAgICAgICAgICAgICAgU1VNKGVuZXJneV9jb25zdW1lZCkgQVMgXCJUb3RhbCBFbmVyZ3kgQ29uc3VtZWRcIgogICAgICAgICAgICBGUk9NIGNvbnN1bXB0aW9uCiAgICAgICAgICAgIFdIRVJFIG1zbiBJTiAoJ1RFQ0NCVVMnLCAnVEVBQ0JVUycsICdURUlDQlVTJywgJ1RFUkNCVVMnKQogICAgICAgICAgICBHUk9VUCBCWSBDT05DQVQoKGRhdGVfeWVhci8xMCk6OmludCAqIDEwLCAncycpLCBkZXNjcmlwdGlvbgogICAgICAgICAgICkKCiAgICAgICAgU0VMRUNUIGMxLmRlY2FkZSwgYzEuXCJTZWN0b3JcIiwgYzEuXCJQcmltYXJ5IEVuZXJneSBDb25zdW1lZFwiLCBjMi5cIlRvdGFsIEVuZXJneSBDb25zdW1lZFwiCiAgICAgICAgRlJPTSBjMQogICAgICAgIExFRlQgSk9JTiBjMgogICAgICAgICAgICBPTiBjMS5cIlNlY3RvclwiID0gYzIuXCJTZWN0b3JcIiBBTkQgYzEuZGVjYWRlID0gYzIuZGVjYWRlCiAgICAgICAgT1JERVIgQlkgYzEuZGVjYWRlLCBjMS5cIlNlY3RvclwiCiAgICAgICAiCgpjb25zdW1lZF9kZiA8LSBkYkdldFF1ZXJ5KGNvbm4sIHNxbCkKCmthYmxlX3N0eWxpbmcoa2FibGUodGFpbChjb25zdW1lZF9kZikpLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48aW1nIHNyYz0iSU1BR0VTL2RvZV9laWEucG5nIiB3aWR0aD0iNzVweCIvPjwvZGl2PgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+Q29uc3VtcHRpb24gYW5kIENPMiBFbWlzc2lvbnM8L3NwYW4+CgoKYGBge3IgZmlnMWUsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSAxNSwgZmlnLmFsaWduID0gImNlbnRlciJ9CnBsb3RfbWF0IDwtIHdpdGgoc3Vic2V0KGNvbnN1bWVkX2RmLCBTZWN0b3IgIT0gJ0VsZWN0cmljIFBvd2VyIFNlY3RvcicpLAogICAgICAgICAgICAgICAgIHRhcHBseShgVG90YWwgRW5lcmd5IENvbnN1bWVkYCwgbGlzdChkZWNhZGUsIGBTZWN0b3JgKSwgc3VtKSkKCnBhcihtYXI9Yyg1LCA1LCAyLCAxKSkKYmFycGxvdChwbG90X21hdCwgbWFpbj0iVS5TLiBUb3RhbCBFbmVyZ3kgQ29uc3VtcHRpb24gYnkgU2VjdG9yIiwgY2V4Lm1haW49MS41LAogICAgICAgIGNvbD1zZWFib3JuX3BhbGV0dGVbMTo4XSwgeWxpbT1jKDAsIDhFNSksIHhheHQ9Im4iLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MSwgYXQ9Yyg1LCAxNCwgMjMsIDMyKSwgbGFiZWxzPWNvbG5hbWVzKHBsb3RfbWF0KSwgcGFkaj0wLjksIHBvcz1jKDAsMCkpCmF4aXMoc2lkZT0yLCBhdD1heFRpY2tzKDIpLCBsYWJlbHM9Zm9ybWF0KGF4VGlja3MoMiksIGJpZy5tYXJrPScsJywgc2NpZW50aWZpYz1GQUxTRSksIGxhcz0xKQpsZWdlbmQoInRvcCIsIHJvdy5uYW1lcyhwbG90X21hdCksIGZpbGw9c2VhYm9ybl9wYWxldHRlWzE6OF0sIG5jb2w9OCkKYGBgCgpgYGB7ciBmaWcxZiwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDE1LCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KcGxvdF9tYXQgPC0gd2l0aChjb25zdW1lZF9kZiwgdGFwcGx5KGBQcmltYXJ5IEVuZXJneSBDb25zdW1lZGAsIGxpc3QoZGVjYWRlLCBgU2VjdG9yYCksIHN1bSkpCgpwYXIobWFyPWMoNSwgNSwgMiwgMSkpCmJhcnBsb3QocGxvdF9tYXQsIG1haW49IlUuUy4gUHJpbWFyeSBFbmVyZ3kgQ29uc3VtcHRpb24gYnkgU2VjdG9yIiwgY2V4Lm1haW49MS41LAogICAgICAgIGNvbD1zZWFib3JuX3BhbGV0dGVbMTo4XSwgeWxpbT1jKDAsIDlFNSksIHhheHQ9Im4iLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MSwgYXQ9Yyg1LCAxNCwgMjMsIDMyLCA0MSksIGxhYmVscz1jb2xuYW1lcyhwbG90X21hdCksIHBhZGo9MC45LCBwb3M9YygwLDApKQpheGlzKHNpZGU9MiwgYXQ9YXhUaWNrcygyKSwgbGFiZWxzPWZvcm1hdChheFRpY2tzKDIpLCBiaWcubWFyaz0nLCcsIHNjaWVudGlmaWM9RkFMU0UpLCBsYXM9MSkKbGVnZW5kKCJ0b3AiLCByb3cubmFtZXMocGxvdF9tYXQpLCBmaWxsPXNlYWJvcm5fcGFsZXR0ZVsxOjhdLCBuY29sPTgpCmBgYAoKYGBge3J9CnNxbCA8LSAiU0VMRUNUIENPTkNBVCgoZGF0ZV95ZWFyLzEwKTo6aW50ICogMTAsICdzJykgYXMgZGVjYWRlLCAKICAgICAgICAgICAgICAgUkVQTEFDRShSRVBMQUNFKGRlc2NyaXB0aW9uLCAnVG90YWwgRW5lcmd5ICcsICcnKSwgJyBDTzIgRW1pc3Npb25zJywgJycpIEFTIFwiU2VjdG9yXCIsCiAgICAgICAgICAgICAgIFNVTShlbmVyZ3lfY28yKSBBUyBcIlRvdGFsIENPMiBFbWlzc2lvbnNcIgogICAgICAgIEZST00gdXNfY28yX2VtaXNzaW9ucwogICAgICAgIFdIRVJFIG1zbiBJTiAoJ1RFUkNFVVMnLCAnVEVDQ0VVUycsICdURUFDRVVTJywgJ1RYRUlFVVMnKQogICAgICAgIEdST1VQIEJZIGRhdGVfeWVhciwgZGVzY3JpcHRpb24KICAgICAgICBPUkRFUiBCWSBkYXRlX3llYXIsIGRlc2NyaXB0aW9uCiAgICAgICAiCgplbWlzc2lvbnNfZGYgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwoZW1pc3Npb25zX2RmKSksCiAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKYGBge3IgZmlnMWcsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSAxNSwgZmlnLmFsaWduID0gImNlbnRlciJ9CnBsb3RfbWF0IDwtIHdpdGgoZW1pc3Npb25zX2RmLCB0YXBwbHkoYFRvdGFsIENPMiBFbWlzc2lvbnNgLCBsaXN0KGRlY2FkZSwgYFNlY3RvcmApLCBzdW0pKQoKcGFyKG1hcj1jKDUsIDgsIDIsIDEpKQpiYXJwbG90KHBsb3RfbWF0LCBtYWluPSJVLlMuIENPMiBFbWlzc2lvbnMgYnkgU2VjdG9yIiwgY2V4Lm1haW49MS41LAogICAgICAgIGNvbD1zZWFib3JuX3BhbGV0dGVbMTo1XSwgeWxpbT1jKDAsIDZFNCksIHhheHQ9Im4iLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MSwgYXQ9YygzLCA5LCAxNSwgMjEpKzAuNSwgbGFiZWxzPWNvbG5hbWVzKHBsb3RfbWF0KSwgcGFkaj0wLjksIHBvcz1jKDAsMCkpCmF4aXMoc2lkZT0yLCBhdD1heFRpY2tzKDIpLCBsYWJlbHM9Zm9ybWF0KGF4VGlja3MoMiksIGJpZy5tYXJrPScsJywgc2NpZW50aWZpYz1GQUxTRSksIGxhcz0xKQpsZWdlbmQoInRvcCIsIHJvdy5uYW1lcyhwbG90X21hdCksIGZpbGw9c2VhYm9ybl9wYWxldHRlWzE6NV0sIG5jb2w9NSkKYGBgCgoKYGBge3J9CnNxbCA8LSAiV0lUSCBzdWIgQVMKICAgICAgICAgIChTRUxFQ1QgQ09OQ0FUKChkYXRlX3llYXIvMTApOjppbnQgKiAxMCwgJ3MnKSBhcyBkZWNhZGUsIAogICAgICAgICAgICAgICAgICBSRVBMQUNFKCQxLCAnJScsICcnKSBhcyBzZWN0b3IsCiAgICAgICAgICAgICAgICAgIFJFUExBQ0UoCiAgICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoCiAgICAgICAgICAgICAgICAgICAgICAgUkVQTEFDRSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoZGVzY3JpcHRpb24sICdDb21tZXJjaWFsIFNlY3RvciBDTzIgRW1pc3Npb25zJywgJycpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdSZXNpZGVudGlhbCBTZWN0b3IgQ08yIEVtaXNzaW9ucycsICcnCiAgICAgICAgICAgICAgICAgICAgICAgKSwgJ1RyYW5zcG9ydGF0aW9uIFNlY3RvciBDTzIgRW1pc3Npb25zJywgJycpLAogICAgICAgICAgICAgICAgICAnICcsICdcbicpICBBUyBcIlR5cGVcIiwKICAgICAgICAgICAgICAgICBlbmVyZ3lfY28yCiAgICAgICAgICAgRlJPTSB1c19jbzJfZW1pc3Npb25zCiAgICAgICAgICAgV0hFUkUgZGVzY3JpcHRpb24gTElLRSAkMikKCiAgICAgICBTRUxFQ1QgZGVjYWRlLCBzZWN0b3IsIFwiVHlwZVwiLCAKICAgICAgICAgICAgICBTVU0oZW5lcmd5X2NvMikgQVMgXCJUb3RhbCBDTzIgRW1pc3Npb25zXCIKICAgICAgIEZST00gc3ViCiAgICAgICBHUk9VUCBCWSBkZWNhZGUsIHNlY3RvciwgXCJUeXBlXCIKICAgICAgIE9SREVSIEJZIGRlY2FkZSwgc2VjdG9yLCBcIlR5cGVcIgogICAgICAgIgoKcGFyYW1zIDwtIHBhc3RlMChjKCIlVHJhbnNwb3J0YXRpb24iLCAiJVJlc2lkZW50aWFsIiwgIiVDb21tZXJjaWFsIiksICIgU2VjdG9yJSIpCmVtaXNzaW9uc190eXBlX2RmIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShwYXJhbXMsIGZ1bmN0aW9uKHApIGRiR2V0UXVlcnkoY29ubiwgc3FsLCBwYXJhbT1saXN0KHAsIHApKSkpCmBgYAoKYGBge3IgZmlnMWgsIGZpZy5oZWlnaHQgPSAxMiwgZmlnLndpZHRoID0gMTUsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQoKcGFyKG1mcm93PWMoMywyKSwgbWFyPWMoNSwgNSwgMiwgMSksIG1haSA9IGMoMC43LCAwLjIsIDAuNywgMC4yKSkKb3V0cHV0IDwtIGJ5KGVtaXNzaW9uc190eXBlX2RmLCBlbWlzc2lvbnNfdHlwZV9kZiRzZWN0b3IsIGZ1bmN0aW9uKHN1YikgewogIHBsb3RfbWF0IDwtIHdpdGgoc3ViLCB0YXBwbHkoYFRvdGFsIENPMiBFbWlzc2lvbnNgLCBsaXN0KGRlY2FkZSwgYFR5cGVgKSwgc3VtKSkKCiAgYmFycGxvdChwbG90X21hdCwgbWFpbj1wYXN0ZSgiVS5TLiBDTzIgRW1pc3Npb25zIGJ5Iiwgc3ViJHNlY3RvcltbMV1dKSwgY2V4Lm1haW49MS41LAogICAgICAgICAgY29sPXNlYWJvcm5fcGFsZXR0ZVsxOjVdLCB5bGltPWMoMCwgY2VpbGluZyhtYXgocGxvdF9tYXQsIG5hLnJtPVRSVUUpLzFFNCkgKiAxRTQpLCAKICAgICAgICAgIHhheHQ9Im4iLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCiAgCiAgYXhpcyhzaWRlPTEsIGF0PWMoMyw5LDE1LDIxLDI3LDMzLDM5LDQ1LDUxLDU3LDYzKVtzZXFfYWxvbmcoY29sbmFtZXMocGxvdF9tYXQpKV0gKyAwLjUsCiAgICAgICBsYWJlbHM9Y29sbmFtZXMocGxvdF9tYXQpLCBwYWRqPTAuOSwgcG9zPWMoMCwwKSkKICBheGlzKHNpZGU9MiwgYXQ9YXhUaWNrcygyKSwgbGFiZWxzPWZvcm1hdChheFRpY2tzKDIpLCBiaWcubWFyaz0nLCcsIHNjaWVudGlmaWM9RkFMU0UpLCBsYXM9MSkKICBsZWdlbmQoInRvcCIsIHJvdy5uYW1lcyhwbG90X21hdCksIGZpbGw9c2VhYm9ybl9wYWxldHRlWzE6NV0sIG5jb2w9NSkKfSkKYGBgCgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGltZyBzcmM9IklNQUdFUy9kb2VfZWlhLnBuZyIgd2lkdGg9Ijc1cHgiLz48L2Rpdj4KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPlJlbmV3YWJsZSBFbmVyZ3k6IFByb2R1Y3Rpb24gYW5kIENvbnN1bXB0aW9uPC9zcGFuPgoKYGBge3J9CnNxbCA8LSAiU0VMRUNUIGVuZXJneV90eXBlLAogICAgICAgICAgICAgICBkYXRlLAogICAgICAgICAgICAgICBTVU0ocHJvZHVjdGlvbikgQVMgcHJvZHVjdGlvbiwKICAgICAgICAgICAgICAgU1VNKGNvbnN1bXB0aW9uKSBBUyBjb25zdW1wdGlvbgogICAgICAgIEZST00gdXNfcmVuZXdhYmxlX2VuZXJneQogICAgICAgIEdST1VQIEJZIGVuZXJneV90eXBlLAogICAgICAgICAgICAgICAgIGRhdGUKICAgICAgICBPUkRFUiBCWSBlbmVyZ3lfdHlwZSwKICAgICAgICAgICAgICAgICBkYXRlCiAgICAgICAiCgpyZW5ld2FibGVfZGYgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwocmVuZXdhYmxlX2RmKSksCiAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKYGBge3IgcmVuZXdhYmxlX2ZpZywgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGggPSAxNSwgZmlnLmFsaWduID0gImNlbnRlciJ9CgpwYXIobWZyb3c9YygzLDMpLCBtYXI9Yyg1LCA1LCAyLCAxKSkKb3V0cHV0IDwtIGJ5KHJlbmV3YWJsZV9kZiwgcmVuZXdhYmxlX2RmJGVuZXJneV90eXBlLCBmdW5jdGlvbihzdWIpIHsKICAKICBtZXRyaWNfdHMgPC0geHRzKHg9c3ViW2MoInByb2R1Y3Rpb24iLCAiY29uc3VtcHRpb24iKV0sIAogICAgICAgICAgICAgICAgICAgb3JkZXIuYnk9c3ViJGRhdGUpCiAgCiAgcHJpbnQocGxvdChtZXRyaWNfdHMsIG1haW4gPSBzdWIkZW5lcmd5X3R5cGVbMV0sCiAgICAgICAgICAgICBsZWdlbmQubG9jPSJib3R0b21yaWdodCIsIAogICAgICAgICAgICAgY29sID0gc2VhYm9ybl9wYWxldHRlWzE6M10sCiAgICAgICAgICAgICB5YXhpcy5yaWdodD1GQUxTRSwKICAgICAgICAgICAgIGF4ZXM9RkFMU0UsCiAgICAgICAgICAgICBsd2Q9MSwKICAgICAgICAgICAgIGNleC5tYWluPTMsCiAgICAgICAgICAgICBtYWpvci50aWNrcz0ieWVhcnMiLAogICAgICAgICAgICAgbWFqb3IuZm9ybWF0PSIlWSIsCiAgICAgICAgICAgICBtaW5vci5mb3JtYXQ9IiVZIiwKICAgICAgICAgICAgIGdyaWQudGlja3MubHR5PTEpKQogIAp9KQpgYGAKCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48YSBocmVmPSJodHRwczovL3dhdGVyZGF0YS51c2dzLmdvdi9ud2lzL2d3Ij48aW1nIHNyYz0iSU1BR0VTL3VzZ3MucG5nIiB3aWR0aD0iNzVweCIvPjwvYT48L2Rpdj4KCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICMzMzY3OTEiLz5VLlMuIEdlb2xvZ2ljYWwgU3VydmV5IC0gR3JvdW5kd2F0ZXIgV2VsbCBEZXB0aCBEYXRhPC9zcGFuPgpgYGB7cn0Kc3FsIDwtICJTRUxFQ1QgZy55ZWFyLCBnLm1vbnRoLAogICAgICAgICAgICAgICBhdmcoZy5tZWFuX3ZhbHVlKSBhcyBtZWFuX3ZhbHVlCiAgICAgICAgRlJPTSBncm91bmR3YXRlciBnCiAgICAgICAgV0hFUkUgZy55ZWFyIEJFVFdFRU4gMTk5MCBBTkQgMjAxOQogICAgICAgIEdST1VQIEJZIGcueWVhciwgZy5tb250aCIKCmdyb3VuZHdhdGVyX2RmIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKa2FibGVfc3R5bGluZyhrYWJsZSh0YWlsKGdyb3VuZHdhdGVyX2RmKSksCiAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKYGBge3IgZmlnMywgZmlnLmhlaWdodCA9IDcsIGZpZy53aWR0aCA9IDE2LCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KYm94cGxvdChtZWFuX3ZhbHVlIH4geWVhciwgZ3JvdW5kd2F0ZXJfZGYsIGNvbD1zZWFib3JuX3BhbGV0dGVbMToxMF0sIAogICAgICAgIG1haW49Ikdyb3VuZHdhdGVyIFdlbGwgRGVwdGggTWVhbiBWYWx1ZXMiLCBjZXgubWFpbj0xLjUpCmBgYAoKCmBgYHtyfQpzcWwgPC0gIldJVEggc2l0ZXMgQVMgKAogICAgICAgICAgICBTRUxFQ1QgQ09OQ0FUKGcueWVhciwgJy0nLCBnLm1vbnRoLCAnLTEnKTo6ZGF0ZSBBUyBcImRhdGVcIiwKICAgICAgICAgICAgICAgICAgIGcuc2l0ZV9uYW1lLAogICAgICAgICAgICAgICAgICAgQVZHKGcubWVhbl92YWx1ZSkgYXMgbWVhbl92YWx1ZQogICAgICAgICAgICBGUk9NIGdyb3VuZHdhdGVyIGcKICAgICAgICAgICAgV0hFUkUgZy55ZWFyIEJFVFdFRU4gMTk5MCBBTkQgMjAxOQogICAgICAgICAgICBHUk9VUCBCWSBDT05DQVQoZy55ZWFyLCAnLScsIGcubW9udGgsICctMScpOjpkYXRlLAogICAgICAgICAgICAgICAgICAgICBnLnNpdGVfbmFtZQogICAgICAgICksIGJvdHRvbSBBUyAoCiAgICAgICAgICAgIFNFTEVDVCBzaXRlX25hbWUsIEFWRyhtZWFuX3ZhbHVlKSBBUyBtZWFuX3ZhbHVlLCAnYm90dG9tX3NpdGVzJyBBUyBjYXRlZ29yeQogICAgICAgICAgICBGUk9NIHNpdGVzIAogICAgICAgICAgICBHUk9VUCBCWSBzaXRlX25hbWUKICAgICAgICAgICAgT1JERVIgQlkgQVZHKG1lYW5fdmFsdWUpIEFTQyBMSU1JVCA1CiAgICAgICAgKSwgdG9wIEFTICgKICAgICAgICAgICAgU0VMRUNUIHNpdGVfbmFtZSwgQVZHKG1lYW5fdmFsdWUpIEFTIG1lYW5fdmFsdWUsICd0b3Bfc2l0ZXMnIEFTIGNhdGVnb3J5CiAgICAgICAgICAgIEZST00gc2l0ZXMgCiAgICAgICAgICAgIEdST1VQIEJZIHNpdGVfbmFtZQogICAgICAgICAgICBPUkRFUiBCWSBBVkcobWVhbl92YWx1ZSkgREVTQyBMSU1JVCA1CiAgICAgICAgKQoKICAgICAgICBTRUxFQ1Qgc2l0ZXMuXCJkYXRlXCIsIHNpdGVzLm1lYW5fdmFsdWUsIHNpdGVzLnNpdGVfbmFtZSwgdG9wLmNhdGVnb3J5CiAgICAgICAgRlJPTSBzaXRlcwogICAgICAgIElOTkVSIEpPSU4gdG9wCiAgICAgICAgICAgIE9OIHNpdGVzLnNpdGVfbmFtZSA9IHRvcC5zaXRlX25hbWUKCiAgICAgICAgVU5JT04KCiAgICAgICAgU0VMRUNUIHNpdGVzLlwiZGF0ZVwiLCBzaXRlcy5tZWFuX3ZhbHVlLCBzaXRlcy5zaXRlX25hbWUsIGJvdHRvbS5jYXRlZ29yeQogICAgICAgIEZST00gc2l0ZXMKICAgICAgICBJTk5FUiBKT0lOIGJvdHRvbQogICAgICAgICAgICBPTiBzaXRlcy5zaXRlX25hbWUgPSBib3R0b20uc2l0ZV9uYW1lCgogICAgICAgIE9SREVSIEJZIGNhdGVnb3J5LCBzaXRlX25hbWUsIFwiZGF0ZVwiIgoKZ3JvdW5kd2F0ZXJfZGYgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgprYWJsZV9zdHlsaW5nKGthYmxlKGhlYWQoZ3JvdW5kd2F0ZXJfZGYpKSwKICAgICAgICAgICAgICBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkKYGBgCgpgYGB7ciBncm91bmR3YXRlcl9maWcsIGZpZy5oZWlnaHQgPSAyNSwgZmlnLndpZHRoID0gMTUsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQoKZ3JvdW5kd2F0ZXJfZGYkeWVhciA8LSBmb3JtYXQoZ3JvdW5kd2F0ZXJfZGYkZGF0ZSwgIiVZIikKCnBhcihtZnJvdz1jKDUsMiksIG1hcj1jKDUsIDUsIDIsIDEpKQpvdXRwdXQgPC0gYnkoZ3JvdW5kd2F0ZXJfZGYsIGdyb3VuZHdhdGVyX2RmJHNpdGVfbmFtZSwgZnVuY3Rpb24oc3ViKSB7CiAgCiAgbWV0cmljX3RzIDwtIHh0cyh4PXN1YltjKCJtZWFuX3ZhbHVlIildLCAKICAgICAgICAgICAgICAgICAgIG9yZGVyLmJ5PXN1YiRkYXRlKQogIAogIHByaW50KHBsb3QobWV0cmljX3RzLCBtYWluID0gcGFzdGUoc3ViJHNpdGVfbmFtZVsxXSwgJzonLCBzdWIkY2F0ZWdvcnlbMV0pLAogICAgICAgICAgICAgbGVnZW5kLmxvYz0iYm90dG9tcmlnaHQiLCAKICAgICAgICAgICAgIGNvbCA9IHNlYWJvcm5fcGFsZXR0ZVsxOjNdLAogICAgICAgICAgICAgeWF4aXMucmlnaHQ9RkFMU0UsCiAgICAgICAgICAgICBheGVzPUZBTFNFLAogICAgICAgICAgICAgbHdkPTEsCiAgICAgICAgICAgICBjZXgubWFpbj0zLAogICAgICAgICAgICAgbWFqb3IudGlja3M9InllYXJzIiwKICAgICAgICAgICAgIG1ham9yLmZvcm1hdD0iJVkiLAogICAgICAgICAgICAgbWlub3IuZm9ybWF0PSIlWSIsCiAgICAgICAgICAgICBncmlkLnRpY2tzLmx0eT0xKSkKICAKfSkKYGBgCgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGEgaHJlZj0iaHR0cHM6Ly93d3cuZndzLmdvdi9lbmRhbmdlcmVkLyIgdGFyZ2V0PSJibGFuayI+PGltZyBzcmM9IklNQUdFUy91c19md3MucG5nIiB3aWR0aD0iNzVweCIvPjwvYT48L2Rpdj4KIyMjIFUuUy4gRldTIEVuZGFuZ2VyZWQgU3BlY2llcyBMaXN0CgpgYGB7cn0Kc3FsIDwtICJTRUxFQ1QgQ09OQ0FUKChmLmRhdGVfeWVhci8xMCk6OmludCAqIDEwLCAncycpIEFTIGRlY2FkZSwgCiAgICAgICAgICAgICAgIGYudGF4b25vbWljX2dyb3VwLAogICAgICAgICAgICAgICBmLmN1cnJlbnRfc3RhdHVzLAogICAgICAgICAgICAgICBjb3VudCgqKSBBUyBzcGVjaWVzX2NvdW50CiAgICAgICAgRlJPTSBmd3Nfc3BlY2llc195ZWFyIGYKICAgICAgICBXSEVSRSBmLmN1cnJlbnRfc3RhdHVzIElOICgnRW5kYW5nZXJlZCcsICdSZWNvdmVyeScsICdSZXNvbHZlZCBUYXhvbicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdUaHJlYXRlbmVkJywgJ0V4dGluY3Rpb24nKQogICAgICAgIEdST1VQIEJZIENPTkNBVCgoZi5kYXRlX3llYXIvMTApOjppbnQgKiAxMCwgJ3MnKSwKICAgICAgICAgICAgICAgICBmLnRheG9ub21pY19ncm91cCwKICAgICAgICAgICAgICAgICBmLmN1cnJlbnRfc3RhdHVzCiAgICAgICAgT1JERVIgQlkgQ09OQ0FUKChmLmRhdGVfeWVhci8xMCk6OmludCAqIDEwLCAncycpLAogICAgICAgICAgICAgICAgIGYudGF4b25vbWljX2dyb3VwLAogICAgICAgICAgICAgICAgIGYuY3VycmVudF9zdGF0dXMiCgpmd3NfZGYgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwoZndzX2RmKSksCiAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKYGBge3IgZndzX2ZpZywgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGggPSAxNSwgZmlnLmFsaWduID0gImNlbnRlciJ9CgpwYXIobWZyb3c9Yyg1LDMpLCBtYXI9Yyg1LCA1LCAyLCAxKSkKb3V0cHV0IDwtIGJ5KGZ3c19kZiwgZndzX2RmJHRheG9ub21pY19ncm91cCwgZnVuY3Rpb24oc3ViKSB7CiAgcGxvdF9tYXQgPC0gd2l0aChzdWIsIHRhcHBseShzcGVjaWVzX2NvdW50LCBsaXN0KGRlY2FkZSwgY3VycmVudF9zdGF0dXMpLCBzdW0pKQogIAogIGJhcnBsb3QocGxvdF9tYXQsIG1haW49c3ViJHRheG9ub21pY19ncm91cFtbMV1dLCBjZXgubWFpbj0xLjUsCiAgICAgICAgICB5bGltID0gYygwLCBtYXgocGxvdF9tYXQsIG5hLnJtPVRSVUUpKzUpLAogICAgICAgICAgY29sPXNlYWJvcm5fcGFsZXR0ZVtzZXFfYWxvbmcocm93Lm5hbWVzKHBsb3RfbWF0KSldLCBiZXNpZGU9VFJVRSkKICBsZWdlbmQoInRvcCIsIHJvdy5uYW1lcyhwbG90X21hdCksIGZpbGw9c2VhYm9ybl9wYWxldHRlW3NlcV9hbG9uZyhyb3cubmFtZXMocGxvdF9tYXQpKV0sIAogICAgICAgICBuY29sPWxlbmd0aChyb3cubmFtZXMocGxvdF9tYXQpKSkKICBib3goKQp9KQpgYGAKCgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGEgaHJlZj0iaHR0cHM6Ly93d3cubmFzcy51c2RhLmdvdi9BZ0NlbnN1cy8iIHRhcmdldD0iYmxhbmsiPjxpbWcgc3JjPSJJTUFHRVMvdXNkYS5wbmciIHdpZHRoPSIxMDBweCIvPjwvYT48L2Rpdj4KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPlUuUy4gRGVwYXJ0bWVudCBvZiBBZ3JpY3VsdHVyZTogQWdyaWN1bHR1cmUgQ2Vuc3VzPC9zcGFuPgoKYGBge3IgZmlnNCwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDE3LCBmaWcuYWxpZ24gPSAiY2VudGVyIn0Kc3FsIDwtICJTRUxFQ1QgeWVhciwKICAgICAgICAgICAgICAgQ0FTRQogICAgICAgICAgICAgICAgICAgIFdIRU4gZG9tYWluX2NhdGVnb3J5ID0gJycgVEhFTiAnVE9UQUxcbk9QRVJBVElPTlMnCiAgICAgICAgICAgICAgICAgICAgRUxTRSBSRVBMQUNFKFJFUExBQ0UoUkVQTEFDRShkb21haW5fY2F0ZWdvcnksICcoJywgJ1xuKCcpLCAnVE8nLCAnVE9cbicpLCAnT1InLCAnT1JcbicpCiAgICAgICAgICAgICAgIEVORCBBUyBkb21haW5fY2F0ZWdvcnksIAogICAgICAgICAgICAgICB2YWx1ZQogICAgICAgIEZST00gYWdfY2Vuc3VzIAogICAgICAgIFdIRVJFIGRhdGFfaXRlbSA9ICdGQVJNIE9QRVJBVElPTlMgLSBOVU1CRVIgT0YgT1BFUkFUSU9OUyciCgphZ2NlbnN1c19kZiA8LSBkYkdldFF1ZXJ5KGNvbm4sIHNxbCkKCnBsb3RfbWF0IDwtIHdpdGgoYWdjZW5zdXNfZGYsIHRhcHBseSh2YWx1ZSwgbGlzdCh5ZWFyLCBkb21haW5fY2F0ZWdvcnkpLCBzdW0pKQoKcGFyKG1hcj1jKDUsIDUsIDIsIDEpKQpiYXJwbG90KHBsb3RfbWF0LCBtYWluPSJVLlMuIEFncmljdWx0dXJlIENlbnN1czogTnVtYmVyIG9mIEZhcm0gT3BlcmF0aW9ucyIsIGNleC5tYWluPTEuNSwKICAgICAgICBjb2w9c2VhYm9ybl9wYWxldHRlWzE6NV0sIHlsaW09YygwLCAyRTYrNUU1KSwgYmVzaWRlPVRSVUUsIHhheHQ9Im4iLCB5YXh0PSJuIikKCmF4aXMoc2lkZT0xLCBhdD1jKDMsOSwxNSwyMSwyNywzMywzOSw0NSkrMC41LCBsYWJlbHM9Y29sbmFtZXMocGxvdF9tYXQpLCB0aWNrPUZBTFNFLCBwYWRqPTAuOSwgcG9zPWMoMCwwKSkKYXhpcyhzaWRlPTIsIGF0PWF4VGlja3MoMiksIGxhYmVscz1mb3JtYXQoYXhUaWNrcygyKSwgYmlnLm1hcms9JywnLCBzY2llbnRpZmljPUZBTFNFKSwgbGFzPTEpCmxlZ2VuZCgidG9wIiwgcm93Lm5hbWVzKHBsb3RfbWF0KSwgZmlsbD1zZWFib3JuX3BhbGV0dGVbMTo1XSwgbmNvbD01KQpgYGAKCmBgYHtyfQpzcWwgPC0gIldJVEggc3ViIEFTICgKICAgICAgICAgIFNFTEVDVCB5ZWFyLAogICAgICAgICAgICAgICAgIGRhdGFfaXRlbSwKICAgICAgICAgICAgICAgICBSRVBMQUNFKAogICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoCiAgICAgICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJFUExBQ0UoZG9tYWluX2NhdGVnb3J5LCAnOycsICcsJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcoJywgJ1xuKCcpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1RPJywgJ1RPXG4nKSwKICAgICAgICAgICAgICAgICAgICAgICAgJyBPUicsICcgT1JcbicpLAogICAgICAgICAgICAgICAgICAgICdMRVNTJywgJ0xFU1NcbicpIEFTIGRvbWFpbl9jYXRlZ29yeSwgCiAgICAgICAgICAgICAgICAgdmFsdWUKICAgICAgICAgIEZST00gYWdfY2Vuc3VzIAogICAgICAgICAgV0hFUkUgZGF0YV9pdGVtIExJS0UgJyVDT01NT0RJVFkgVE9UQUxTJScKICAgICAgICAgICAgQU5EIHZhbHVlIElTIE5PVCBOVUxMCiAgICAgICApIAoKICAgICAgIFNFTEVDVCB5ZWFyLCAKICAgICAgICAgICAgICBDQVNFCiAgICAgICAgICAgICAgICAgIFdIRU4gVFJJTShkb21haW5fY2F0ZWdvcnkpID0gJycgCiAgICAgICAgICAgICAgICAgIFRIRU4gQ0FTRSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRU4gZGF0YV9pdGVtID0gJ0NPTU1PRElUWSBUT1RBTFMgLSBTQUxFUzsgTUVBU1VSRUQgSU4gJCcgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBUSEVOICdUT1RBTFxuU0FMRVMnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVOIGRhdGFfaXRlbSA9ICdDT01NT0RJVFkgVE9UQUxTIC0gU0FMRVM7IE1FQVNVUkVEIElOICQgLyBPUEVSQVRJT04nIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgVEhFTiAnVE9UQUxcblNBTEVTXG5QRVIgT1BFUkFUSU9OJwogICAgICAgICAgICAgICAgICAgICAgIEVORAogICAgICAgICAgICAgICAgICBFTFNFIGRvbWFpbl9jYXRlZ29yeSAKICAgICAgICAgICAgICBFTkQgQVMgZG9tYWluX2NhdGVnb3J5LAogICAgICAgICAgICAgIHZhbHVlCiAgICAgICBGUk9NIHN1YgogICAgICAiCgphZ2NlbnN1c19kZiA8LSB3aXRoaW4oZGJHZXRRdWVyeShjb25uLCBzcWwpLAogICAgICAgICAgICAgICAgICAgICAgZG9tYWluX2NhdGVnb3J5IDwtIGZhY3Rvcihkb21haW5fY2F0ZWdvcnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkZBUk0gU0FMRVM6IFxuKExFU1NcbiBUSEFOIDIsNTAwICQpIiwgIkZBUk0gU0FMRVM6IFxuKDIsNTAwIFRPXG4gNCw5OTkgJCkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRkFSTSBTQUxFUzogXG4oNSwwMDAgVE9cbiA5LDk5OSAkKSIsICJGQVJNIFNBTEVTOiBcbigxMCwwMDAgVE9cbiAyNCw5OTkgJCkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGQVJNIFNBTEVTOiBcbigyNSwwMDAgVE9cbiA0OSw5OTkgJCkiLCAiRkFSTSBTQUxFUzogXG4oNTAsMDAwIFRPXG4gOTksOTk5ICQpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRkFSTSBTQUxFUzogXG4oMTAwLDAwMCBUT1xuIDQ5OSw5OTkgJCkiLCAiRkFSTSBTQUxFUzogXG4oNTAwLDAwMCBPUlxuIE1PUkUgJCkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVE9UQUxcblNBTEVTIiwgIlRPVEFMXG5TQUxFU1xuUEVSIE9QRVJBVElPTiIpKQopCgprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwoYWdjZW5zdXNfZGYpKSwKICAgICAgICAgICAgICBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkKYGBgCgpgYGB7ciBmaWc1LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTcsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpwbG90X21hdCA8LSB3aXRoKGFnY2Vuc3VzX2RmW2FnY2Vuc3VzX2RmJGRvbWFpbl9jYXRlZ29yeSAhPSAnVE9UQUxcblNBTEVTJyxdLCAKICAgICAgICAgICAgICAgICB0YXBwbHkodmFsdWUsIGxpc3QoeWVhciwgZmFjdG9yKGRvbWFpbl9jYXRlZ29yeSkpLCBzdW0pKQoKcGFyKG1hcj1jKDUsIDUsIDIsIDEpKQpiYXJwbG90KHBsb3RfbWF0LCBtYWluPSJVLlMuIEFncmljdWx0dXJlIENlbnN1czogQ29tbW9kaXR5IFRvdGFscyAtIEZhcm0gU2FsZXMiLCBjZXgubWFpbj0xLjUsCiAgICAgICAgY29sPXNlYWJvcm5fcGFsZXR0ZVsxOjVdLCB5bGltPWMoMCwgMUU2KSwgYmVzaWRlPVRSVUUsIHhheHQ9Im4iLCB5YXh0PSJuIikKCmF4aXMoc2lkZT0xLCBhdD1jKDMsOSwxNSwyMSwyNywzMywzOSw0NSw1MSkrMC41LCBsYWJlbHM9Y29sbmFtZXMocGxvdF9tYXQpLCB0aWNrPUZBTFNFLCBwYWRqPTAuOSwgcG9zPWMoMCwwKSkKYXhpcyhzaWRlPTIsIGF0PWF4VGlja3MoMiksIGxhYmVscz1mb3JtYXQoYXhUaWNrcygyKSwgYmlnLm1hcms9JywnLCBzY2llbnRpZmljPUZBTFNFKSwgbGFzPTEpCmxlZ2VuZCgidG9wIiwgcm93Lm5hbWVzKHBsb3RfbWF0KSwgZmlsbD1zZWFib3JuX3BhbGV0dGVbMTo1XSwgbmNvbD01KQpgYGAKCgpgYGB7ciBmaWc2LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTUsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpwbG90X21hdCA8LSB3aXRoKGFnY2Vuc3VzX2RmW2FnY2Vuc3VzX2RmJGRvbWFpbl9jYXRlZ29yeSA9PSAnVE9UQUxcblNBTEVTJyxdLCAKICAgICAgICAgICAgICAgICB0YXBwbHkodmFsdWUsIGxpc3QoeWVhciwgZmFjdG9yKGRvbWFpbl9jYXRlZ29yeSkpLCBzdW0pKQoKcGFyKG1hcj1jKDUsIDgsIDIsIDEpKQpiYXJwbG90KHBsb3RfbWF0LCBtYWluPSJVLlMuIEFncmljdWx0dXJlIENlbnN1czogQ29tbW9kaXR5IFRvdGFscyAtIE92ZXJhbGwgRmFybSBTYWxlcyIsIGNleC5tYWluPTEuNSwKICAgICAgICBjb2w9c2VhYm9ybl9wYWxldHRlWzE6NV0sIHlsaW09YygwLCA0RTExKzVFMTApLCBiZXNpZGU9VFJVRSwgeGF4dD0ibiIsIHlheHQ9Im4iLCBzcGFjZT0yKQogICAgICAgIApheGlzKHNpZGU9MSwgYXQ9YygyLjUsNS41LDguNSwxMS41LDE0LjUpLCBsYWJlbHM9cm93Lm5hbWVzKHBsb3RfbWF0KSwgcGFkaj0wLjksIHBvcz1jKDAsMCkpCmF4aXMoc2lkZT0yLCBhdD1heFRpY2tzKDIpLCBsYWJlbHM9Zm9ybWF0KGF4VGlja3MoMiksIGJpZy5tYXJrPScsJywgc2NpZW50aWZpYz1GQUxTRSksIGxhcz0xKQpsZWdlbmQoInRvcCIsIHJvdy5uYW1lcyhwbG90X21hdCksIGZpbGw9c2VhYm9ybl9wYWxldHRlWzE6NV0sIG5jb2w9NSkKYGBgCgpgYGB7cn0Kc3FsIDwtICJTRUxFQ1QgeWVhciwgCiAgICAgICAgICAgICAgIFJFUExBQ0UoUkVQTEFDRShkYXRhX2l0ZW0sICcgLSBPUEVSQVRJT05TIFdJVEggQVJFQSBIQVJWRVNURUQnLCAnJyksICc7IEdSQUlOJywgJycpIGFzIGNyb3AsCiAgICAgICAgICAgICAgIHZhbHVlCiAgICAgICAgRlJPTSBhZ19jZW5zdXMKICAgICAgICBXSEVSRSBkYXRhX2l0ZW0gSU4gKCdDT1JOOyBHUkFJTiAtIE9QRVJBVElPTlMgV0lUSCBBUkVBIEhBUlZFU1RFRCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnV0hFQVQgLSBPUEVSQVRJT05TIFdJVEggQVJFQSBIQVJWRVNURUQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NPWUJFQU5TIC0gT1BFUkFUSU9OUyBXSVRIIEFSRUEgSEFSVkVTVEVEJykKICAgICAgICBPUkRFUiBCWSB5ZWFyLCBkYXRhX2l0ZW0KICAgICAgICIKCmNyb3BzX2RmIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKa2FibGVfc3R5bGluZyhrYWJsZSh0YWlsKGNyb3BzX2RmKSksCiAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKYGBge3IgZmlnNywgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDE1LCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KcGxvdF9tYXQgPC0gd2l0aChjcm9wc19kZiwgdGFwcGx5KHZhbHVlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoeWVhciwgZmFjdG9yKGNyb3AsIGxldmVscz1jKCJXSEVBVCIsICJTT1lCRUFOUyIsICJDT1JOIikpKSwgc3VtKQopCgpwYXIobWFyPWMoNSwgOCwgMiwgMSkpCmJhcnBsb3QocGxvdF9tYXQsIG1haW49IlUuUy4gQWdyaWN1bHR1cmUgQ2Vuc3VzOiBOdW1iZXIgb2YgT3BlcmF0aW9ucyBieSBTcGVjaWZpYyBDcm9wcyIsIGNleC5tYWluPTEuNSwKICAgICAgICBjb2w9c2VhYm9ybl9wYWxldHRlWzE6NV0sIHlsaW09YygwLCA1RTUpLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MiwgYXQ9YXhUaWNrcygyKSwgbGFiZWxzPWZvcm1hdChheFRpY2tzKDIpLCBiaWcubWFyaz0nLCcsIHNjaWVudGlmaWM9RkFMU0UpLCBsYXM9MSkKbGVnZW5kKCJ0b3AiLCByb3cubmFtZXMocGxvdF9tYXQpLCBmaWxsPXNlYWJvcm5fcGFsZXR0ZVsxOjVdLCBuY29sPTUpCmBgYAoKYGBge3J9CnNxbCA8LSAiU0VMRUNUIHllYXIsIAogICAgICAgICAgICAgICBSRVBMQUNFKAogICAgICAgICAgICAgICAgICBSRVBMQUNFKAogICAgICAgICAgICAgICAgICAgICBSRVBMQUNFKGRhdGFfaXRlbSwgJyAtIE9QRVJBVElPTlMgV0lUSCBTQUxFUycsICcnKSwgCiAgICAgICAgICAgICAgICAgICAgICc7IElOQ0wgQ0FMVkVTJywgJycpLAogICAgICAgICAgICAgICAgICAnOyBCUk9JTEVSUycsICcnKSBhcyBsaXZlc3RvY2ssCiAgICAgICAgICAgICAgIHZhbHVlCiAgICAgICAgRlJPTSBhZ19jZW5zdXMKICAgICAgICBXSEVSRSBkYXRhX2l0ZW0gSU4gKCdDQVRUTEU7IElOQ0wgQ0FMVkVTIC0gT1BFUkFUSU9OUyBXSVRIIFNBTEVTJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdIT0dTIC0gT1BFUkFUSU9OUyBXSVRIIFNBTEVTJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdDSElDS0VOUzsgQlJPSUxFUlMgLSBPUEVSQVRJT05TIFdJVEggU0FMRVMnKQogICAgICAgIE9SREVSIEJZIHllYXIsIGRhdGFfaXRlbQogICAgICAgIgoKbGl2ZXN0b2NrX2RmIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKa2FibGVfc3R5bGluZyhrYWJsZSh0YWlsKGxpdmVzdG9ja19kZikpLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCgpgYGB7ciBmaWc4LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTUsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpwbG90X21hdCA8LSB3aXRoKGxpdmVzdG9ja19kZiwgdGFwcGx5KHZhbHVlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoeWVhciwgZmFjdG9yKGxpdmVzdG9jaywgbGV2ZWxzPWMoICJDSElDS0VOUyIsICJIT0dTIiwgIkNBVFRMRSIpKSksIHN1bSkKKQoKcGFyKG1hcj1jKDUsIDgsIDIsIDEpKQpiYXJwbG90KHBsb3RfbWF0LCBtYWluPSJVLlMuIEFncmljdWx0dXJlIENlbnN1czogTnVtYmVyIG9mIE9wZXJhdGlvbnMgYnkgU3BlY2lmaWMgTGl2ZXN0b2NrIiwgY2V4Lm1haW49MS41LAogICAgICAgIGNvbD1zZWFib3JuX3BhbGV0dGVbMTo1XSwgeWxpbT1jKDAsIDFFNisyRTUpLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MiwgYXQ9YXhUaWNrcygyKSwgbGFiZWxzPWZvcm1hdChheFRpY2tzKDIpLCBiaWcubWFyaz0nLCcsIHNjaWVudGlmaWM9RkFMU0UpLCBsYXM9MSkKbGVnZW5kKCJ0b3AiLCByb3cubmFtZXMocGxvdF9tYXQpLCBmaWxsPXNlYWJvcm5fcGFsZXR0ZVsxOjVdLCBuY29sPTUpCmBgYAoKCmBgYHtyfQpzcWwgPC0gIlNFTEVDVCB5ZWFyLCAKICAgICAgICAgICAgICAgQ0FTRQogICAgICAgICAgICAgICAgICAgV0hFTiBkYXRhX2l0ZW0gPSAnQ1JPUCBUT1RBTFMgLSBTQUxFUzsgTUVBU1VSRUQgSU4gJCcgVEhFTiAnQ1JPUCBUT1RBTFMnCiAgICAgICAgICAgICAgICAgICBXSEVOIGRhdGFfaXRlbSA9ICdBTklNQUwgVE9UQUxTOyBJTkNMIFBST0RVQ1RTIC0gU0FMRVM7IE1FQVNVUkVEIElOICQnIFRIRU4gJ0FOSU1BTCBUT1RBTFMnCiAgICAgICAgICAgICAgIEVORCBhcyBjYXRlZ29yeSwKICAgICAgICAgICAgICAgdmFsdWUKICAgICAgICBGUk9NIGFnX2NlbnN1cwogICAgICAgIFdIRVJFIGRhdGFfaXRlbSBJTiAoJ0NST1AgVE9UQUxTIC0gU0FMRVM7IE1FQVNVUkVEIElOICQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0FOSU1BTCBUT1RBTFM7IElOQ0wgUFJPRFVDVFMgLSBTQUxFUzsgTUVBU1VSRUQgSU4gJCcpCiAgICAgICAgT1JERVIgQlkgeWVhciwgZGF0YV9pdGVtCiAgICAgICAiCgphZ3RvdGFsX2RmIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKa2FibGVfc3R5bGluZyhrYWJsZSh0YWlsKGFndG90YWxfZGYpKSwKICAgICAgICAgICAgICBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkKYGBgCgpgYGB7ciBmaWc5LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTUsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpwbG90X21hdCA8LSB3aXRoKGFndG90YWxfZGYsIHRhcHBseSh2YWx1ZSwgbGlzdCh5ZWFyLCBjYXRlZ29yeSksIHN1bSkpCgpwYXIobWFyPWMoNSwgOCwgMiwgMSkpCmJhcnBsb3QocGxvdF9tYXQsIG1haW49IlUuUy4gQWdyaWN1bHR1cmUgQ2Vuc3VzOiBDcm9wIHZzIEFuaW1hbCBTYWxlcyIsIGNleC5tYWluPTEuNSwKICAgICAgICBjb2w9c2VhYm9ybl9wYWxldHRlWzE6NV0sIHlsaW09YygwLCAyRTExKzVFMTApLCB5YXh0PSJuIiwgYmVzaWRlPVRSVUUpCgpheGlzKHNpZGU9MiwgYXQ9YXhUaWNrcygyKSwgbGFiZWxzPWZvcm1hdChheFRpY2tzKDIpLCBiaWcubWFyaz0nLCcsIHNjaWVudGlmaWM9RkFMU0UpLCBsYXM9MSkKbGVnZW5kKCJ0b3AiLCByb3cubmFtZXMocGxvdF9tYXQpLCBmaWxsPXNlYWJvcm5fcGFsZXR0ZVsxOjVdLCBuY29sPTUpCmBgYAoKPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQiPjxpbWcgc3JjPSJJTUFHRVMvd29ybGRfbWV0cmljcy5wbmciIHdpZHRoPSIzMDBweCIvPjwvZGl2PgojIyMgV29ybGQgTWV0cmljcwoKYGBge3J9CnNxbCA8LSAiV0lUSCBwb3AgQVMgCiAgICAgICAgICAgIChTRUxFQ1QgcC55ZWFyLAogICAgICAgICAgICAgICAgICAgIHAucG9wdWxhdGlvbgogICAgICAgICAgICAgRlJPTSB3b3JsZF9wb3B1bGF0aW9uIHAKICAgICAgICAgICAgIFdIRVJFIHAuY291bnRyeV9uYW1lID0gJ1dvcmxkJwogICAgICAgICAgICAgICBBTkQgcC55ZWFyIEJFVFdFRU4gMjAwMCBBTkQgMjAxOQogICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAKICAgICAgICAgICAgICBsYW5kIEFTCiAgICAgICAgICAgIChTRUxFQ1QgYS55ZWFyLAogICAgICAgICAgICAgICAgICAgIGEucGVyY2VudF9hcmFibGUKICAgICAgICAgICAgIEZST00gYXJhYmxlX2xhbmQgYQogICAgICAgICAgICAgV0hFUkUgYS5jb3VudHJ5X25hbWUgPSAnV29ybGQnCiAgICAgICAgICAgICAgIEFORCBhLnllYXIgQkVUV0VFTiAyMDAwIEFORCAyMDE5CiAgICAgICAgICAgICApLAogICAgICAgICAKICAgICAgICAgICAgICBmYXVuYSBBUwogICAgICAgICAgICAoU0VMRUNUIGkueWVhciwKICAgICAgICAgICAgICAgICAgICBTVU0oaS5zcGVjaWVzX2NvdW50KSBBUyBhbmltYWxzX2NvdW50CiAgICAgICAgICAgICBGUk9NIGl1Y25fc3BlY2llc19jb3VudCBpCiAgICAgICAgICAgICBXSEVSRSBpLnllYXIgQkVUV0VFTiAyMDAwIEFORCAyMDE5CiAgICAgICAgICAgICBHUk9VUCBCWSBpLnllYXIKICAgICAgICAgICAgICksCiAgICAgICAgIAogICAgICAgICAgICAgIGZsb3JhIEFTCiAgICAgICAgICAgIChTRUxFQ1QgcC5hc3Nlc3NtZW50X3llYXIgYXMgeWVhciwKICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBBUyBwbGFudHNfY291bnQKICAgICAgICAgICAgIEZST00gcGxhbnRzX2Fzc2Vzc21lbnRzIHAKICAgICAgICAgICAgIFdIRVJFIHAuYXNzZXNzbWVudF95ZWFyIEJFVFdFRU4gMjAwMCBBTkQgMjAxOQogICAgICAgICAgICAgICBBTkQgcC5pbnRlcnByZXRlZF9zdGF0dXMgPSAnVGhyZWF0ZW5lZCcKICAgICAgICAgICAgIEdST1VQIEJZIHAuYXNzZXNzbWVudF95ZWFyCiAgICAgICAgICAgICApLCAgICAgICAgICAgICAKICAgICAgICAgCiAgICAgICAgICAgICAgaWNlIEFTCiAgICAgICAgICAgIChTRUxFQ1Qgcy5kYXRlX3llYXIgYXMgeWVhciwKICAgICAgICAgICAgICAgICAgICBBVkcocy5leHRlbnQpIEZJTFRFUihXSEVSRSBzLnJlZ2lvbiA9ICdBcmN0aWMnKSBBUyBhcmN0aWNfc2VhX2ljZV9leHRlbnQsCiAgICAgICAgICAgICAgICAgICAgQVZHKHMuZXh0ZW50KSBGSUxURVIoV0hFUkUgcy5yZWdpb24gPSAnQW50YXJjdGljYScpIEFTIGFudGFyY3RpY19zZWFfaWNlX2V4dGVudAogICAgICAgICAgICAgRlJPTSBzZWFfaWNlX2V4dGVudCBzCiAgICAgICAgICAgICBXSEVSRSBzLmRhdGVfeWVhciBCRVRXRUVOIDIwMDAgQU5EIDIwMTkKICAgICAgICAgICAgIEdST1VQIEJZIHMuZGF0ZV95ZWFyCiAgICAgICAgICAgICApLAogICAgICAgICAgICAgCiAgICAgICAgICAgICAgb2NlYW4gQVMKICAgICAgICAgICAgKFNFTEVDVCBvLnllYXIgYXMgeWVhciwKICAgICAgICAgICAgICAgICAgICBBVkcoby50Y28yKSBBUyB0b3RhbF9jbzIsCiAgICAgICAgICAgICAgICAgICAgQVZHKG8ucGh0czI1cDApIEFTIHBoX3NjYWxlCiAgICAgICAgICAgICBGUk9NIG9jZWFuX2RhdGEgbwogICAgICAgICAgICAgV0hFUkUgby55ZWFyIEJFVFdFRU4gMjAwMCBBTkQgMjAxOQogICAgICAgICAgICAgICBBTkQgby50Y28yIDw+IC05OTk5IEFORCBvLnBodHMyNXAwIDw+IC05OTk5CiAgICAgICAgICAgICBHUk9VUCBCWSBvLnllYXIKICAgICAgICAgICAgICksCiAgICAgICAgICAgICAKICAgICAgICAgICAgICB0ZW1wIEFTCiAgICAgICAgICAgIChTRUxFQ1QgZy55ZWFyIGFzIHllYXIsCiAgICAgICAgICAgICAgICAgICAgQVZHKGcuZ2xvYmFsX21lYW4pIEFTIGdsb2JhbF9tZWFuCiAgICAgICAgICAgICBGUk9NIGdsb2JhbF90ZW1wZXJhdHVyZSBnCiAgICAgICAgICAgICBXSEVSRSBnLnllYXIgQkVUV0VFTiAyMDAwIEFORCAyMDE5CiAgICAgICAgICAgICBHUk9VUCBCWSBnLnllYXIKICAgICAgICAgICAgICkKICAgICAgICAgICAgIAogICAgICAgICBTRUxFQ1QgcG9wLnllYXIsIHBvcC5wb3B1bGF0aW9uLCBsYW5kLnBlcmNlbnRfYXJhYmxlLCBmYXVuYS5hbmltYWxzX2NvdW50LCBmbG9yYS5wbGFudHNfY291bnQsCiAgICAgICAgICAgICAgICBpY2UuYXJjdGljX3NlYV9pY2VfZXh0ZW50LCBpY2UuYW50YXJjdGljX3NlYV9pY2VfZXh0ZW50LCAKICAgICAgICAgICAgICAgIG9jZWFuLnRvdGFsX2NvMiwgb2NlYW4ucGhfc2NhbGUsIHRlbXAuZ2xvYmFsX21lYW4KICAgICAgICAgRlJPTSBwb3AgCiAgICAgICAgIEpPSU4gbGFuZCBVU0lORyAoeWVhcikKICAgICAgICAgSk9JTiBmYXVuYSBVU0lORyAoeWVhcikKICAgICAgICAgSk9JTiBmbG9yYSBVU0lORyAoeWVhcikKICAgICAgICAgSk9JTiBpY2UgVVNJTkcgKHllYXIpCiAgICAgICAgIEpPSU4gb2NlYW4gVVNJTkcgKHllYXIpCiAgICAgICAgIEpPSU4gdGVtcCBVU0lORyAoeWVhcikKICAgICAgICAgT1JERVIgQlkgcG9wLnllYXIiCgplbnZfd29ybGRfZGYgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgprYWJsZV9zdHlsaW5nKGthYmxlKHRhaWwoZW52X3dvcmxkX2RmKSksIGZvbnRfc2l6ZSA9IDEyLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCmBgYHtyfQprYWJsZV9zdHlsaW5nKGthYmxlKGNvcihlbnZfd29ybGRfZGZbLTFdKSksIGZvbnRfc2l6ZSA9IDEyLAogICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQpgYGAKCmBgYHtyIGZpZzEwLCBmaWcuaGVpZ2h0ID0gMTUsIGZpZy53aWR0aCA9IDE1LCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KcGFyKG1mcm93PWMoNCwyKSwgbWFpID0gYygwLjMsIDAuMiwgMC43LCAwLjIpKQoKZm9yKHggaW4gY29sbmFtZXMoZW52X3dvcmxkX2RmKVszOm5jb2woZW52X3dvcmxkX2RmKV0pIHsKICBsZml0IDwtIGxvZXNzKHBhc3RlKHgsICJ+IHBvcHVsYXRpb24iKSwgZGF0YT1lbnZfd29ybGRfZGYpCiAgCiAgcGxvdChhcy5mb3JtdWxhKHBhc3RlKHgsICJ+IHBvcHVsYXRpb24iKSksIGVudl93b3JsZF9kZiwgCiAgICAgICBtYWluPXBhc3RlKCJwb3B1bGF0aW9uIGFuZFxuIiwgZ3N1YigiXyIsICIgIiwgeCkpLCBjZXgubWFpbj0yLAogICAgICAgdHlwZT0icCIsIGNvbD1zZWFib3JuX3BhbGV0dGVbMV0sIHlheHQ9J24nLCB4YXh0PSduJywgcGNoPTE5KQogIGF4aXMoc2lkZT0xLCBhdD1heFRpY2tzKDEpLCBsYWJlbHM9Zm9ybWF0KGF4VGlja3MoMSksIGJpZy5tYXJrPScsJywgc2NpZW50aWZpYz1GQUxTRSkpCiAgYXhpcyhzaWRlPTIsIGF0PWF4VGlja3MoMiksIGxhYmVscz1mb3JtYXQoYXhUaWNrcygyKSwgYmlnLm1hcms9JywnLCBzY2llbnRpZmljPUZBTFNFKSkKICBwb3Bfb3JkZXIgPC0gb3JkZXIoZW52X3dvcmxkX2RmJHBvcHVsYXRpb24pCiAgbGluZXMoZW52X3dvcmxkX2RmJHBvcHVsYXRpb25bcG9wX29yZGVyXSwgbGZpdCRmaXR0ZWRbcG9wX29yZGVyXSwgY29sPXNlYWJvcm5fcGFsZXR0ZVs0XSwgbHdkPTMpCn0KCmBgYAoKCmBgYHtyfQpkYkRpc2Nvbm5lY3QoY29ubikKYGBgCgo8ZGl2IHN0eWxlPSJmbG9hdDpyaWdodCI+PGltZyBzcmM9IklNQUdFUy9yX3NoaW55LnBuZyIvPjwvZGl2PgoKIyMjIFVzZXIgRGF0YSBBcHBsaWNhdGlvbgoKPGRpdiBzdHlsZT0iZmxvYXQ6Y2VudGVyIj48aW1nIHNyYz0iSU1BR0VTL2Vudl9kYXRhLnBuZyIgd2lkdGg9IjQwMHB4Ii8+PC9kaXY+CgpgYGB7cn0KCiMgTElOVVggU0hFTEwgQ09NTUFORCBDQUxMCnN5c3RlbShwYXN0ZTAoImdub21lLXRlcm1pbmFsIC0tIFJzY3JpcHQgLWUgXCJsaWJyYXJ5KHNoaW55KTsgc2V0d2QoJyIsIGdldHdkKCksICInKTsgcnVuQXBwKCdFbnZpcm9ubWVudERCX1NoaW55X0FwcC5SJylcIiIpKQoKYGBgCgo8aHIgc3R5bGU9ImJvcmRlcjogbm9uZTsgaGVpZ2h0OiAxcHg7IGJhY2tncm91bmQtY29sb3I6ICNDQ0M7Ii8+Cgo8aDI+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+Q29uY2x1c2lvbjwvc3Bhbj48L2gyPgoKPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQ7Ij48aW1nIHNyYz0iSU1BR0VTL2RhdGFfcGlwZWxpbmUucG5nIiB3aWR0aD0iMzUwcHgiLz48L2Rpdj4KPGJyLz4KCjxkaXYgc3R5bGU9ImZsb2F0OmxlZnQ7Ij48aW1nIHNyYz0iSU1BR0VTL3Bvc3RncmVzcWxfci5wbmciIHdpZHRoPSIxMDBweCIvPjwvZGl2Pgo8YnIvPgo8YnIvPgoKLSA8aDM+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+UG9zdGdyZXMgYXMgdG9vbCBpbiBkYXRhIHNjaWVuY2Ugd29ya2Zsb3c8L2gzPgotIDxoMz48c3BhbiBzdHlsZT0iY29sb3I6ICMzMzY3OTEiLz5EYXRhIHBlcnNpc3RlbmNlIGFuZCBoeWdpZW5lPC9oMz4KLSA8aDM+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+Q2VudHJhbGl6YXRpb24gYW5kIHNlY3VyaXR5PC9oMz4KLSA8aDM+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMzM2NzkxIi8+RWZmaWNpZW5jeSBhbmQgdXNlZnVsbmVzczwvaDM+Ci0gPGgzPjxzcGFuIHN0eWxlPSJjb2xvcjogIzMzNjc5MSIvPlNjYWxhYmlsaXR5IGFuZCBhcHBsaWNhYmlsaXR5PC9oMz4KCjxici8+Cjxici8+CgoK